使用 java 生成 openssl 密钥对
Generate openssl keypair using java
我需要在 java 中生成模拟以下内容的 openssl 密钥对:
openssl ecparam -name prime256v1 -genkey -noout -out prime256v1.key
openssl pkcs8 -topk8 -in prime256v1.key -out prime256v1-priv.pem -nocrypt
openssl ec -in prime256v1-priv.pem -pubout -out prime256v1-pub.pem
我的java程序如下:
public static void main(String args[]) throws Exception{
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
g.initialize(spec);
KeyPair keyPair = g.generateKeyPair();
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
String publicKeyContent = Base64.encode(publicKeyBytes);
String publicKeyFormatted = "-----BEGIN PUBLIC KEY-----" + System.lineSeparator();
for (final String row:
Splitter
.fixedLength(64)
.split(publicKeyContent)
)
{
publicKeyFormatted += row + System.lineSeparator();
}
publicKeyFormatted += "-----END PUBLIC KEY-----";
BufferedWriter writer = new BufferedWriter(new FileWriter("publickey.pem"));
writer.write(publicKeyFormatted);
writer.close();
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
String privateKeyContent = Base64.encode(privateKeyBytes);
String privateKeyFormatted = "-----BEGIN PRIVATE KEY-----" + System.lineSeparator();
for (final String row:
Splitter
.fixedLength(64)
.split(privateKeyContent)
)
{
privateKeyFormatted += row + System.lineSeparator();
}
privateKeyFormatted += "-----END PRIVATE KEY-----";
BufferedWriter writer2 = new BufferedWriter(new FileWriter("privatekey.pem"));
writer2.write(privateKeyFormatted);
writer2.close();
}
以上代码有效,但生成的私钥似乎比通过我在顶部提到的命令行实用程序生成的私钥更长。
带命令行的私钥:
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGuyf3+/6+rnDKw0D
WbxVyggwNL0jlTVAzGm6cpl3ji2hRANCAAQ7zLtxLLvl6LJHJAlYAZr4hAc09fZn
bAniYIeKVqVBdKIvb5R445PFiUDFcfyneeX/resPXJHMEm/vAxfQeMqL
-----END PRIVATE KEY-----
私钥 java:
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgYFPrkmxnwjVBgpUV
B02/luLD1rt9
UWZHj62YdhwYQESgCgYIKoZIzj0DAQehRANCAATZp7Jl8KXXApA
hvv9qeQtX5LbHQkrCdx3DfkUC
GgCUMSJWKxs7yJPNKtFZnFUTFZfyEF76fdEzky
zIon5H04MX
-----END PRIVATE KEY-----
即使我在这里删除了 2 行额外的行,即使那样,这似乎是一个更大的密钥。
Public命令行键:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEO8y7cSy75eiyRyQJWAGa+IQHNPX2
Z2wJ4mCHilalQXSiL2+UeOOTxYlAxXH8p3nl/63rD1yRzBJv7wMX0HjKiw==
-----END PUBLIC KEY-----
Public 键与 Java:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2aeyZfCl1wKQIb7/ankLV+S2x0JK
wncdw35FAhoA
lDEiVisbO8iTzSrRWZxVExWX8hBe+n3RM5MsyKJ+R9ODFw==
-----END PUBLIC KEY-----
所以,我的第一个问题是关于私钥长度。好像更长了
我的第二个问题是我似乎没有正确拆分生成的密钥字节。线路肯定比预期的要多。如何纠正?
... the private key length ... seems longer
是,准确的说是结构representing/containing私钥比较长。 Java 包括 PKCS8 中的可选 -- 和不必要的(冗余) -- 'parameters' 算法字段-具体数据,是SEC1 appendix C.4中定义的ECPrivateKey,而OpenSSL则没有。这将在读回时被忽略。两个结构中的实际键值是正确的大小。
I am not splitting the generated key bytes properly
而是拆分编码(密钥结构的)字节的 (base64) 字符。
查看 Base64.encode
在 Splitter
之前的输出。我打赌你会发现它已经在每 76 个 base64 字符之后包含换行符,符合 MIME 标准(RFC 1521 et seq),有些人认为它更常见(或更重要?)或至少比 PEM 更新。 (虽然 XML 和 JWX 甚至更新并且现在很常见,并且根本不插入换行符。)结果你的 Splitter
需要:
- 从第一行开始的前 64 个字符
- 第一行剩下的 12 个字符,换行符和第二行的 51 个字符
- 第二行剩下的 25 个字符,一个换行符,以及第三行(最多)38 个字符
- 等等
虽然 OpenSSL 写入 PEM 文件,每 64 个字符(最后一行除外),根据 PEM 标准(RFC 1421 ), 它始终能够 读取 字符数为 4 的任意倍数的文件,最多 76 个字符,与 MIME 一致。自 2016 年 1.1.0 以来的最新版本,现在被相当广泛地采用,可以读取多达数百个字符的行。因此,如果您的文件要被(任何使用)OpenSSL 库读取,您可以只编写 split-at-76 版本而无需任何进一步更改,除非确保有一个换行符终止 last 线。其他软件可能有所不同;如果您需要安全或严格合规,请先从 Base64.encode
输出中 删除 换行符,然后以正确的 64 间距将它们添加回去。请参阅 the recently published respecification.
PS:如果您使用 Java 将此密钥放入 PKCS12 keystore(这需要您 have/get/create 它的证书), openssl commandline 可以直接读取,并将 (1) 私钥转换为 PEM,(2) 将证书转换为 PEM,您可以从中提取 PEM 中的公钥。
我需要在 java 中生成模拟以下内容的 openssl 密钥对:
openssl ecparam -name prime256v1 -genkey -noout -out prime256v1.key
openssl pkcs8 -topk8 -in prime256v1.key -out prime256v1-priv.pem -nocrypt
openssl ec -in prime256v1-priv.pem -pubout -out prime256v1-pub.pem
我的java程序如下:
public static void main(String args[]) throws Exception{
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
g.initialize(spec);
KeyPair keyPair = g.generateKeyPair();
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
String publicKeyContent = Base64.encode(publicKeyBytes);
String publicKeyFormatted = "-----BEGIN PUBLIC KEY-----" + System.lineSeparator();
for (final String row:
Splitter
.fixedLength(64)
.split(publicKeyContent)
)
{
publicKeyFormatted += row + System.lineSeparator();
}
publicKeyFormatted += "-----END PUBLIC KEY-----";
BufferedWriter writer = new BufferedWriter(new FileWriter("publickey.pem"));
writer.write(publicKeyFormatted);
writer.close();
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
String privateKeyContent = Base64.encode(privateKeyBytes);
String privateKeyFormatted = "-----BEGIN PRIVATE KEY-----" + System.lineSeparator();
for (final String row:
Splitter
.fixedLength(64)
.split(privateKeyContent)
)
{
privateKeyFormatted += row + System.lineSeparator();
}
privateKeyFormatted += "-----END PRIVATE KEY-----";
BufferedWriter writer2 = new BufferedWriter(new FileWriter("privatekey.pem"));
writer2.write(privateKeyFormatted);
writer2.close();
}
以上代码有效,但生成的私钥似乎比通过我在顶部提到的命令行实用程序生成的私钥更长。
带命令行的私钥:
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGuyf3+/6+rnDKw0D
WbxVyggwNL0jlTVAzGm6cpl3ji2hRANCAAQ7zLtxLLvl6LJHJAlYAZr4hAc09fZn
bAniYIeKVqVBdKIvb5R445PFiUDFcfyneeX/resPXJHMEm/vAxfQeMqL
-----END PRIVATE KEY-----
私钥 java:
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgYFPrkmxnwjVBgpUV
B02/luLD1rt9
UWZHj62YdhwYQESgCgYIKoZIzj0DAQehRANCAATZp7Jl8KXXApA
hvv9qeQtX5LbHQkrCdx3DfkUC
GgCUMSJWKxs7yJPNKtFZnFUTFZfyEF76fdEzky
zIon5H04MX
-----END PRIVATE KEY-----
即使我在这里删除了 2 行额外的行,即使那样,这似乎是一个更大的密钥。
Public命令行键:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEO8y7cSy75eiyRyQJWAGa+IQHNPX2
Z2wJ4mCHilalQXSiL2+UeOOTxYlAxXH8p3nl/63rD1yRzBJv7wMX0HjKiw==
-----END PUBLIC KEY-----
Public 键与 Java:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2aeyZfCl1wKQIb7/ankLV+S2x0JK
wncdw35FAhoA
lDEiVisbO8iTzSrRWZxVExWX8hBe+n3RM5MsyKJ+R9ODFw==
-----END PUBLIC KEY-----
所以,我的第一个问题是关于私钥长度。好像更长了 我的第二个问题是我似乎没有正确拆分生成的密钥字节。线路肯定比预期的要多。如何纠正?
... the private key length ... seems longer
是,准确的说是结构representing/containing私钥比较长。 Java 包括 PKCS8 中的可选 -- 和不必要的(冗余) -- 'parameters' 算法字段-具体数据,是SEC1 appendix C.4中定义的ECPrivateKey,而OpenSSL则没有。这将在读回时被忽略。两个结构中的实际键值是正确的大小。
I am not splitting the generated key bytes properly
而是拆分编码(密钥结构的)字节的 (base64) 字符。
查看 Base64.encode
在 Splitter
之前的输出。我打赌你会发现它已经在每 76 个 base64 字符之后包含换行符,符合 MIME 标准(RFC 1521 et seq),有些人认为它更常见(或更重要?)或至少比 PEM 更新。 (虽然 XML 和 JWX 甚至更新并且现在很常见,并且根本不插入换行符。)结果你的 Splitter
需要:
- 从第一行开始的前 64 个字符
- 第一行剩下的 12 个字符,换行符和第二行的 51 个字符
- 第二行剩下的 25 个字符,一个换行符,以及第三行(最多)38 个字符
- 等等
虽然 OpenSSL 写入 PEM 文件,每 64 个字符(最后一行除外),根据 PEM 标准(RFC 1421 ), 它始终能够 读取 字符数为 4 的任意倍数的文件,最多 76 个字符,与 MIME 一致。自 2016 年 1.1.0 以来的最新版本,现在被相当广泛地采用,可以读取多达数百个字符的行。因此,如果您的文件要被(任何使用)OpenSSL 库读取,您可以只编写 split-at-76 版本而无需任何进一步更改,除非确保有一个换行符终止 last 线。其他软件可能有所不同;如果您需要安全或严格合规,请先从 Base64.encode
输出中 删除 换行符,然后以正确的 64 间距将它们添加回去。请参阅 the recently published respecification.
PS:如果您使用 Java 将此密钥放入 PKCS12 keystore(这需要您 have/get/create 它的证书), openssl commandline 可以直接读取,并将 (1) 私钥转换为 PEM,(2) 将证书转换为 PEM,您可以从中提取 PEM 中的公钥。