假设我使用openssl创建了一个x25519密钥对,它将输出一个64字节的私钥和相应的44字节Base64编码的公钥,看起来像
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEIMBF8S7zUco4bRrMiIuyTcSYU/rAVlNtE8SMYWphUatw
-----END PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VuAyEAE0eiiP0PKjy9AVM/0z2ZIZn453WSJNemrQ58HAXDaX0=
-----END PUBLIC KEY-----
Swift CryptoKit只接受32字节的私钥和公钥初始化。
如果我理解正确的话,64字节的私钥就是种子,其中前32字节是实际的私钥。
尽管如此,对公钥使用相同的原理是不起作用的(这并不奇怪(
现在的问题是:如何将公钥转换为Swift CryptoKit所需的32字节?
下面是使用base64解码公钥的前32个字节的不起作用的例子
let base64PublicKey = Data(base64Encoded: "MCowBQYDK2VuAyEAE0eiiP0PKjy9AVM/0z2ZIZn453WSJNemrQ58HAXDaX0=")!.dropLast(12)
let publicKey = try! Curve25519.KeyAgreement.PublicKey(rawRepresentation: rawPublicKey)
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEIMBF8S7zUco4bRrMiIuyTcSYU/rAVlNtE8SMYWphUatw
-----END PRIVATE KEY-----
per-rfc7468是一个PKCS8未加密的PrivateKeyInfo,它被编码在ASN.1 DER中,并包含关于算法的数据(通常但这里不是参数(以及实际密钥。将其运行到openssl asn1parse -i
(它会自动去base64(中,会产生
0:d=0 hl=2 l= 46 cons: SEQUENCE
2:d=1 hl=2 l= 1 prim: INTEGER :00
5:d=1 hl=2 l= 5 cons: SEQUENCE
7:d=2 hl=2 l= 3 prim: OBJECT :X25519
12:d=1 hl=2 l= 34 prim: OCTET STRING [HEX DUMP]:0420C045F12EF351CA386D1ACC888BB24DC49853FAC056536D13C48C616A6151AB70
特定于算法的私钥是OCETSTRING,其值在偏移12+2处,长度为34,但它实际上包含嵌套的OCETSTRIN编码,其前两个八位字节为04=标记和20=长度,因此真正的私钥在偏移16处,长度32,或者更简单地说是最后32个字节。
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VuAyEAE0eiiP0PKjy9AVM/0z2ZIZn453WSJNemrQ58HAXDaX0=
-----END PUBLIC KEY-----
类似地是由X.509和PKIX定义的SubjectPublicKeyInfo结构,其类似地是DER并且除了密钥之外还包含数据。分析它(使用-dump
(得到:
0:d=0 hl=2 l= 42 cons: SEQUENCE
2:d=1 hl=2 l= 5 cons: SEQUENCE
4:d=2 hl=2 l= 3 prim: OBJECT :X25519
9:d=1 hl=2 l= 33 prim: BIT STRING
0000 - 00 13 47 a2 88 fd 0f 2a-3c bd 01 53 3f d3 3d 99 ..G....*<..S?.=.
0010 - 21 99 f8 e7 75 92 24 d7-a6 ad 0e 7c 1c 05 c3 69 !...u.$....|...i
0020 - 7d
BITSTRING值的第一个八位位组用于未使用/填充位的数量,这里为00,因此真正的公钥值是偏移量9+2+1=12处的33-1=32个八位位位组,或者再次是最后32个字节。
Ed25519对私钥进行散列处理,以产生一个32字节的标量(有时称为种子(和一个确定公钥的32字节值。这个种子可以与私钥一起存储,以提高签名效率,但OpenSSL对Ed25519没有这样做,而且它根本不适用于X25519。