我正在开发一个Web应用程序,该应用程序必须能够在服务器端使用ECC加密数据并在浏览器中解密。我发现唯一能够在JS中做到这一点的库是SJCL。但是,由于 SJCL 中的 ECC 支持目前似乎有点被放弃,因此我使用了一个分支,它具有密钥序列化支持和一个演示,以便于理解。
首先,我在 JS 中生成一个 ECC 密钥对:
keypair = sjcl.ecc.elGamal.generateKeys(384, 10);
document.writeln(JSON.stringify(keypair.pub.serialize()));
这将输出如下内容:
{"point":[1110230655,241884220,775655552,-849225963,-883815628,-1984298210,-736346431,1387519594,-1810604283,-1235638489,1333314084,-1219216530,614640565,-1148742381,1038670260,1013716131,758346573,1162278003,1232401864,-1948620456,533899535,-1478577959,1853846180,-1553049184],"curve":384}
然后我尝试将此公钥转换为 OpenSSL 可以理解的格式。
ar = [1110230655,241884220,775655552,-849225963,-883815628,-1984298210,-736346431,1387519594,-1810604283,-1235638489,1333314084,-1219216530,614640565,-1148742381,1038670260,1013716131,758346573,1162278003,1232401864,-1948620456,533899535,-1478577959,1853846180,-1553049184]
# ugly bit magic to somehow convert the above array into a proper byte array (in form of a string)
kstr = [(ar.map { |i| (i>=0)?('0'*(8-i.to_s(16).length)+i.to_s(16)):("%08X" % (2**32-1+i+1)) }*'').upcase].pack("H*")
# opening a public key generated with the openssl cli tool showed a structure like this:
algokey = OpenSSL::ASN1::ObjectId 'id-ecPublicKey'
algovalue = OpenSSL::ASN1::ObjectId 'secp384r1'
algo = OpenSSL::ASN1::Sequence.new [algokey,algovalue]
# for some reason OpenSSL seems to prepend 0x04 to all public keys
key = OpenSSL::ASN1::BitString.new "x04#{kstr}"
root = OpenSSL::ASN1::Sequence.new [algo,key]
pub = OpenSSL::PKey.read(root.to_der)
到目前为止,我的代码工作正常。也就是说,它不会产生任何异常。
但是,在与两个库生成共享密钥时,我发现 SJCL 生成了一个 96 字节长的"标签",而 OpenSSL 发出了 48 字节。
事实证明,我的问题是SJCL不使用普通的ECDH。它使用的东西似乎是基于快速谷歌搜索的ECMQV。因此,"标签"SJCL输出是曲线上的一个点(一个点的x和y坐标,2 * 48字节),而OpenSSL输出是一个共享的秘密(一个点的x坐标,由ECDH规定)。
我的问题是我不知道OpenSSL中是否支持ECMQV(如果我是对的,有一些专利问题)。即使有,红宝石绑定似乎也不支持它。
所以我的实际问题:
- 我上面记录的发现是否正确?
- 如果是,有谁知道我可以使用任何其他支持 ECMQV 的 Ruby 库来代替 OpenSSL?
看起来你在javascript代码中使用了ElGamal。我真的找不到 ruby 的任何实现,替代方案是使用 Crypto++ 或 libgcrypt 并编写一些胶水代码。
Ps:代替那kstr =
行,你可以简单地写kstr = ar.pack 'N*'