问题描述
如果我使用此代码:
<div class="iw ajw">
text before
<span translate="no" class="hb">to <span email="[email protected]" name="Mark" data-hovercard-id="[email protected]" class="g2" data-hovercard-owner-id="119">Mark</span>
</span>
text after
</div>
我得到了这个结果:
let enc = new TextEncoder();
let data = enc.encode('January February');
let algorithm = {
name: 'AES-CBC',iv: enc.encode('0123456789ABCDEF')
};
crypto.subtle.importKey(
'raw',enc.encode('GHIJKLMnopQRSTUV'),'AES-CBC',true,['encrypt']
).then(
key => crypto.subtle.encrypt(algorithm,key,data)
).then(
ct => console.log(btoa(String.fromCharCode(...new Uint8Array(ct))))
);
q6BAetimbeLcdlSC7GoBbtrh/HM4xs3t1+BzEYxdEIk=
但是 PHP 可以选择禁用填充:
echo openssl_encrypt(
'January February','aes-128-cbc','GHIJKLMnopQRSTUV',iv: '0123456789ABCDEF'
);
结果:
echo openssl_encrypt(
'January February',OPENSSL_ZERO_PADDING,'0123456789ABCDEF'
);
可以使用 JavaScript 禁用填充吗?如果没有,什么是获得 同样较短的输出?
解决方法
WebCrypto 默认在 AES-CBC PKCS7 padding 的上下文中使用(here),据我所知不能禁用(也是 SubtleCrypto.encrypt()
的文档)。
只有在明文是块大小的整数倍(AES 为 16 字节)时,才可以使用 AES-CBC 进行无填充加密,如您的示例所示。在这种情况下,PKCS7 会附加一个带有填充字节(都是 0x10)的完整块,即在密文中只需要删除最后一个块:
//
// Your code (implicit base64 encoding)
//
$enc = openssl_encrypt('January February','aes-128-cbc','GHIJKLMNOPQRSTUV',OPENSSL_ZERO_PADDING,'0123456789ABCDEF');
print($enc . PHP_EOL);
//
// Remove last block (explicit Base64 encoding required,since the last block of the ACTUAL ciphertext must be removed)
//
$enc = base64_encode(substr(openssl_encrypt('January February',OPENSSL_RAW_DATA,'0123456789ABCDEF'),-16));
print($enc . PHP_EOL);
和
let enc = new TextEncoder();
let data = enc.encode('January February');
let algorithm = {
name: 'AES-CBC',iv: enc.encode('0123456789ABCDEF')
};
crypto.subtle.importKey(
'raw',enc.encode('GHIJKLMNOPQRSTUV'),'AES-CBC',true,['encrypt']
).then(
key => crypto.subtle.encrypt(algorithm,key,data)
).then(
ct => console.log(btoa(String.fromCharCode(...new Uint8Array(ct).slice(0,-16)))) // reomve last block
);
两个代码段都提供 q6BAetimbeLcdlSC7GoBbg==
作为输出。
应该注意的是,解密成本更高,因为必须添加加密填充字节(如果解密后没有有效的 PKCS7 填充,则会抛出 DOMException
)。
当然有一些 JavaScript 库比低级 WebCrypto API 更舒适,并且还支持不同的填充以及禁用填充,例如CryptoJS:
var data = 'January February';
var key = CryptoJS.enc.Utf8.parse('GHIJKLMNOPQRSTUV');
var iv = CryptoJS.enc.Utf8.parse('0123456789ABCDEF');
var encrypted = CryptoJS.AES.encrypt(
data,{
iv: iv,padding: CryptoJS.pad.NoPadding
}
);
console.log(encrypted.toString());
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
其他答案很好,但作为替代方案,您可以使用内置节点
crypto
API:
let crypto = require('crypto');
let [pt,iv] = ['January February','0123456789ABCDEF'];
// default is padding,omit last method if needed
let cipher = crypto.createCipheriv('aes-128-cbc',iv).setAutoPadding(false);
let ct = cipher.update(pt,'utf8','base64') + cipher.final('base64');
console.log(ct);
这是一个使用 Deno crypto
库的示例:
import { Aes } from 'http://deno.land/x/crypto/aes.ts';
import { Cbc,Padding } from 'http://deno.land/x/crypto/block-modes.ts';
let te = new TextEncoder;
let [pt,iv] = [
te.encode('January February'),te.encode('GHIJKLMNOPQRSTUV'),te.encode('0123456789ABCDEF')
];
// default is no padding,omit last argument if needed
let cipher = new Cbc(Aes,iv,Padding.PKCS7);
let ct = btoa(String.fromCharCode(...cipher.encrypt(pt)));
console.log(ct);