前端开发中经常遇到这样一个需求:用户点击按钮后,把一段包含敏感信息的内容复制到剪贴板,比如临时密码、分享令牌、一次性取件码等。如果直接用navigator.clipboard.writeText把明文写入,那么任何能读取剪贴板的程序都可以拿到这段内容,风险不小。这篇文章就来聊聊如何在浏览器端对写入剪贴板的内容先做加密,再执行写入,让剪贴板里躺着的是一段密文而非裸数据。

一、先弄清楚HTML5剪贴板API的工作机制
HTML5之后,浏览器提供了两套与剪贴板交互的方式。一套是传统的document.execCommand('copy'),配合copy事件和clipboardData.setData使用;另一套是更现代的navigator.clipboard异步API,核心方法是writeText和write。前者虽然被标记为废弃,但兼容性极好;后者需要HTTPS环境或者localhost,并且依赖用户手势触发,否则会抛出权限错误。
加密写入的思路其实很直接:在调用写入方法之前,先用Web Crypto API对明文做一次加密运算,把得到的密文(通常是字节数组)转成Base64字符串,再写入剪贴板。这样即使剪贴板被第三方程序读取,看到的也只是一串无法直接理解的乱码。读取方拿到密文后,配合约定的密钥执行解密,即可还原原始内容。
需要注意的是,剪贴板本身没有任何加密能力,它只是一个跨进程共享的数据缓冲区。操作系统层面的剪贴板对所有进程可见(Windows上可以通过OpenClipboard直接读取),所以加密这一步必须由应用层自己完成,这也是本文方案的价值所在。
二、用Web Crypto实现AES-GCM加密
浏览器内置的crypto.subtle对象提供了完整的加密能力,推荐使用AES-GCM算法,它同时提供保密性和完整性校验,比纯AES-CBC更安全。加密流程分三步:生成或导入密钥、执行加密、把密文编码为可传输的字符串。
下面是一段完整的加密写入示例代码:
// 将字符串编码为字节数组
function str2buf(str) {
return new TextEncoder().encode(str);
}
// 将字节数组转为Base64字符串
function buf2b64(buf) {
return btoa(String.fromCharCode(...new Uint8Array(buf)));
}
// 生成AES-GCM密钥(实际项目中应复用同一密钥)
async function getKey() {
return await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
}
// 加密并写入剪贴板
async function encryptAndCopy(plaintext) {
const key = await getKey();
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
key,
str2buf(plaintext)
);
// 将iv拼接在密文前,一起Base64编码,方便解密方提取
const merged = new Uint8Array(iv.length + encrypted.byteLength);
merged.set(iv, 0);
merged.set(new Uint8Array(encrypted), iv.length);
const cipherText = buf2b64(merged.buffer);
await navigator.clipboard.writeText(cipherText);
console.log('已加密写入剪贴板');
}这段代码有几个细节值得注意。第一,AES-GCM要求每次加密使用不同的IV(初始化向量),这里用crypto.getRandomValues生成12字节的随机IV。第二,IV本身不是秘密,可以和密文一起传输,所以示例中把IV拼接在密文前面,解密方先取前12字节作为IV,剩下的才是真正的密文。第三,generateKey每次调用都会生成新密钥,如果希望粘贴方能够解密,就必须通过某种约定方式共享密钥,这引出了下面的密钥管理问题。
三、解密端如何还原剪贴板内容
加密写入了,还要能解密读出来才有意义。如果读取方同样是网页应用,可以直接使用navigator.clipboard.readText拿到密文,再执行解密。示例代码如下:
// Base64字符串转字节数组
function b642buf(b64) {
const bin = atob(b64);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) {
bytes[i] = bin.charCodeAt(i);
}
return bytes;
}
// 解密剪贴板中的密文
async function readAndDecrypt(key) {
const cipherB64 = await navigator.clipboard.readText();
const merged = b642buf(cipherB64);
const iv = merged.slice(0, 12);
const data = merged.slice(12);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv },
key,
data
);
return new TextDecoder().decode(decrypted);
}这里有个前置条件:readText需要用户授权剪贴板读取权限,浏览器会弹出授权提示。此外,如果密钥是在页面中动态生成的,跨页面解密时需要先把密钥导出。可以用crypto.subtle.exportKey('raw', key)把密钥导出为原始字节,再配合用户输入的口令做PBKDF2派生,这样密钥不必硬编码在前端代码里。基于口令派生密钥的写法如下:
// 用口令派生AES密钥(PBKDF2)
async function deriveKey(password, salt) {
const keyMaterial = await crypto.subtle.importKey(
'raw',
str2buf(password),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}四、兼容性处理与安全注意事项
兼容性方面,navigator.clipboard在Chrome、Edge、Firefox较新版本中都已支持,但Safari对readText的限制较严,且整个API必须在安全上下文(HTTPS或localhost)中运行。对于旧浏览器,可以做降级处理:监听copy事件,在事件回调中调用event.clipboardData.setData('text/plain', cipherText),配合event.preventDefault()阻止默认复制行为。降级方案的代码结构如下:
// 降级方案:通过copy事件写入
document.addEventListener('copy', (e) => {
if (window.pendingCipher) {
e.clipboardData.setData('text/plain', window.pendingCipher);
e.preventDefault();
window.pendingCipher = null;
}
});
// 触发降级复制
function legacyCopy(cipherText) {
window.pendingCipher = cipherText;
document.execCommand('copy');
}安全层面有几点必须提醒。首先,前端的任何加密都无法对抗恶意脚本,如果页面本身被注入了代码,加密前的明文早就暴露了,这个方案防的是剪贴板这一环节的泄露,而不是全链路安全。其次,密钥绝不能硬编码在JavaScript文件中,因为前端代码对所有人可见,推荐使用口令派生或从服务端按需下发(通过HTTPS)。再次,加密后的Base64字符串长度会明显膨胀,如果剪贴板内容还要被其他系统消费,需要提前约定好格式,比如加上类似ENC1:这样的前缀标识,方便识别这是加密数据。
最后总结一下整体流程:明文通过AES-GCM加密得到密文,IV拼接后Base64编码写入剪贴板,读取方获取权限后取出密文,用共享密钥解密还原。整个过程中密钥管理是最关键的一环,只要密钥不泄露,剪贴板中的内容就是安全的。这套方案实现成本低,全部依赖浏览器原生API,无需引入任何第三方加密库,适合密码临时复制、敏感配置分发等场景。
HTML5剪贴板加密Clipboard APIWeb Crypto修改时间:2026-09-02 05:08:30