无需登录 数据私有 本地保存

子资源完整性哈希生成器 - SRI 校验码在线计算

8
0
0
0
快捷选择常用 CDN 资源:
jQuery 3.7.1 Bootstrap 5.3 JS Bootstrap 5.3 CSS Font Awesome 6.5 Vue 3 React 18 Alpine.js Lodash
SRI Integrity 值
输入资源信息并点击计算按钮
关于 SRI 子资源完整性的常见问题
SRI(Subresource Integrity,子资源完整性)是一项 W3C 标准安全功能,允许浏览器在加载外部资源(如 CDN 上的 JavaScript 或 CSS 文件)时,验证资源内容是否与预期一致。

通过在 <script><link> 标签中添加 integrity 属性,浏览器会在资源下载完成后计算其哈希值,并与属性中的值进行比对。如果不匹配,浏览器将拒绝执行该资源,从而防止 CDN 被篡改或资源被恶意替换带来的安全风险。
使用 SRI 主要有以下几个重要原因:

1. 安全防护:防止 CDN 服务被入侵后分发恶意代码,保护网站访问者的安全。
2. 供应链安全:确保第三方资源在传输过程中未被篡改,即使 CDN 提供商本身也无法绕过验证。
3. 合规要求:许多安全标准(如 PCI-DSS、OWASP 推荐)都建议使用 SRI 来增强网页安全性。
4. 零成本:添加 SRI 只需在标签中增加一个属性,几乎不影响性能,却能显著提升安全性。
这三种都是 SHA-2 家族的加密哈希算法,主要区别在于输出长度和安全性:

SHA-256:输出 256 位(32 字节)哈希值,计算速度最快,安全性足够应对绝大多数场景。
SHA-384:输出 384 位(48 字节)哈希值,是 目前 SRI 推荐的默认算法,平衡了安全性和性能。
SHA-512:输出 512 位(64 字节)哈希值,安全性最高但计算稍慢,适合对安全要求极高的场景。

建议:一般情况下选择 SHA-384,它已被广泛采用且是 SRI 规范中的推荐算法。
使用 SRI 非常简单,只需在引用外部资源的标签中添加 integritycrossorigin 属性:

JavaScript 文件:
<script src="https://cdn.example.com/lib.min.js" integrity="sha384-xxxxx" crossorigin="anonymous"></script>

CSS 文件:
<link rel="stylesheet" href="https://cdn.example.com/style.min.css" integrity="sha384-xxxxx" crossorigin="anonymous">

注意:crossorigin="anonymous" 属性是必需的(除非资源与页面同源),否则浏览器不会执行 SRI 验证。
crossorigin="anonymous" 告诉浏览器以 CORS(跨域资源共享)方式请求资源,但不发送用户凭据(如 Cookies)。

SRI 需要这个属性的原因是:当资源来自不同域时,如果浏览器以"同源策略"模式加载资源,某些错误信息会被屏蔽,SRI 验证失败时浏览器可能无法正确报告错误。设置 crossorigin 确保浏览器能够正确处理 SRI 验证流程。

如果资源与页面同源,则不需要此属性。
SRI 在所有现代浏览器中都得到了良好支持,包括:

✅ Chrome 45+
✅ Firefox 43+
✅ Safari 11+
✅ Edge 17+
✅ Opera 32+

对于不支持 SRI 的老旧浏览器,integrity 属性会被忽略,资源仍会正常加载(只是没有完整性验证)。这使得 SRI 可以安全地作为"渐进增强"功能使用。
如果 CDN 上的资源内容发生变化(即使是微小的更新),其哈希值也会完全不同。浏览器检测到哈希不匹配后,会阻止该资源加载,并在控制台输出错误信息。

因此,在更新 CDN 资源版本时,必须同时更新 integrity 属性。这就是为什么建议:
• 引用资源时固定版本号(如 jquery@3.7.1 而非 jquery@latest
• 更新版本时,使用本工具重新生成 SRI 哈希值
• 在开发流程中集成 SRI 自动更新机制
当然可以!本工具支持上传本地文件粘贴内容来生成 SRI 哈希值。

对于本地构建的 JS/CSS 文件,您可以:
1. 使用"上传文件"模式,直接选择本地文件
2. 使用"粘贴内容"模式,复制文件内容到文本框

生成的 integrity 值可以用于自建 CDN 或分发到第三方平台的资源。另外,许多构建工具(如 Webpack、Vite)也有 SRI 插件可以自动为打包产物生成 integrity 属性。
'; } } function clearResult() { $resultPlaceholder.show(); $resultContent.hide(); $integrityText.text(''); $integrityBox.removeClass('highlight'); $tagBox.hide(); $tagText.text(''); $tagBadgeScript.hide(); $tagBadgeLink.hide(); $algoBadge.text(''); $crossoriginHint.hide(); lastIntegrity = ''; lastTag = ''; } // 复制按钮 $tool.on('click', '.sri-copy-btn', function() { var targetId = $(this).data('target'); var text = $('#' + targetId).text().trim(); if (!text) return; copyToClipboard(text).then(function() { var $btn = $(this); $btn.addClass('copied'); $btn.find('i').removeClass('fa-copy').addClass('fa-check'); showToast('已复制到剪贴板'); setTimeout(function() { $btn.removeClass('copied'); $btn.find('i').removeClass('fa-check').addClass('fa-copy'); }, 2000); }.bind(this)).catch(function() { showToast('复制失败,请手动选择复制', 'danger'); }); }); function copyToClipboard(text) { if (navigator.clipboard && navigator.clipboard.writeText) { return navigator.clipboard.writeText(text); } return new Promise(function(resolve, reject) { var textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.select(); try { document.execCommand('copy'); resolve(); } catch(e) { reject(e); } document.body.removeChild(textarea); }); } // Toast function showToast(msg, type) { type = type || 'success'; $toast.removeClass('text-bg-success text-bg-warning text-bg-danger'); if (type === 'success') $toast.addClass('text-bg-success'); else if (type === 'warning') $toast.addClass('text-bg-warning'); else $toast.addClass('text-bg-danger'); $toastMsg.text(msg); toastInstance.show(); } function shakeElement($el) { $el.addClass('shake-animation'); $el.css({ animation: 'sri-shake 0.5s ease' }); setTimeout(function() { $el.css({ animation: '' }); }, 500); } // 加载状态 function showLoading($btn, isLoading) { if (isLoading) { $btn.data('original-html', $btn.html()); $btn.html(' 加载中...'); } else { var orig = $btn.data('original-html'); if (orig) $btn.html(orig); } } // 历史记录 function addHistory(item) { // 去重 historyItems = historyItems.filter(function(h) { return h.integrity !== item.integrity; }); historyItems.unshift(item); if (historyItems.length > MAX_HISTORY) historyItems = historyItems.slice(0, MAX_HISTORY); try { localStorage.setItem('sri-tool-history', JSON.stringify(historyItems)); } catch(e) {} renderHistory(); } function renderHistory() { if (historyItems.length === 0) { $historyCard.hide(); return; } $historyCard.show(); var html = ''; historyItems.forEach(function(item, idx) { var shortIntegrity = item.integrity.substring(0, 40) + '...'; var shortUrl = item.url.length > 50 ? item.url.substring(0, 47) + '...' : item.url; var timeAgo = getTimeAgo(item.time); html += '
'; html += '' + item.algo + ''; html += '' + escapeHtml(shortIntegrity) + ''; html += '' + timeAgo + ''; html += ''; html += '
'; }); $historyList.html(html); // 悬停效果 $historyList.find('.sri-history-item').hover( function() { $(this).css({ background: '#f9fafb', borderColor: '#e5e7eb' }); }, function() { $(this).css({ background: 'transparent', borderColor: 'transparent' }); } ); } // 历史记录点击 $historyList.on('click', '.sri-history-item', function(e) { if ($(e.target).closest('.sri-hist-copy').length) return; var idx = $(this).data('idx'); if (idx !== undefined && historyItems[idx]) { var item = historyItems[idx]; lastIntegrity = item.integrity; lastTag = item.tag; $integrityText.text(item.integrity); $integrityBox.addClass('highlight'); $resultPlaceholder.hide(); $resultContent.show(); $algoBadge.text('算法: ' + item.algo); if (item.tag) { $tagText.text(item.tag); $tagBox.show(); $crossoriginHint.show(); if (item.tag.indexOf('