跳到主要内容

Rsa 密匙对生成

· 阅读需 7 分钟

前言

生成 rsa 密匙对有许多中方式,而在系统中使用最多的就是 openssl。如果你的操作系统还没有 openssl,就需要手动安装。

Windows 用户需要到官网 https://www.openssl.org 进行下载安装。

Unix 用户可以直接使用默认的包管理工具进行安装。具体可以参考:https://command-not-found.com/openssl,这里提供了各种发行版对应的安装命令。

OpenSSL 生成 RSA 密钥对

生成私钥

$ openssl genrsa -out private_key.pem length
  • private_key.pem:输出的私钥文件名
  • length:指定私钥长度(如 1024、2048),越长表示加密性越强。

openssl genrsa 命令默认生成的是 PEM 格式私钥文件,PEM (Privacy-Enhanced Mail) 是一种常见的密钥和证书格式,它使用 Base64 编码的 ASCII 文本表示密钥和证书,并且通常以 .pem 作为文件扩展名。

提示

需要特别说明说明的一点是,openssl 在 3.0 之前 openssl genrsa 命令生成的私钥文件默认使用的是 PKCS#1 标准。但是从 3.0 开始,默认为 PKCS#8 标准。

如何区分生成的私钥是 PKCS#1 标准还是 PKCS#8 标准呢?只需要打开生成的私钥文件,看开头和结尾内容标识即可:

PKCS#1 标准文件内容开头和结尾标识:

-----BEGIN RSA PRIVATE KEY-----
.....
-----END RSA PRIVATE KEY-----

PKCS#8 标准文件内容开头和结尾标识:

-----BEGIN PRIVATE KEY-----
.....
-----END PRIVATE KEY-----

示例:

$ openssl genrsa -out private_key.pem 2048

命令执行完成之后就会看到在当前目录下多了一个 private_key.pem 私钥文件:

$ ls
private_key.pem

如果私钥文件内容以 “-----BEGIN RSA PRIVATE KEY-----” 开头,“-----END RSA PRIVATE KEY-----” 结尾,说明私钥是 PKCS#1 标准。

如果私钥文件内容以 “-----BEGIN PRIVATE KEY-----” 开头,“-----END PRIVATE KEY-----” 结尾,说明私钥是 PKCS#8 标准。

导出公钥(默认X.509格式)

从 rsa 私钥中提取公钥,命令如下:

$ openssl rsa -in private_key.pem -pubout -out public_key.pem

private_key.pem 就是之前生成的私钥的文件名(公钥生成规则是通过计算私钥数值)。public_key.pem 表示要导出的公钥的文件。

之后的就产生了对应的公钥文件 id_rsa_pub:

$ ls
private_key.pem public_key.pem

私钥转 PKCS8 格式

PKCS#8 格式是一种更通用的私钥格式标准(如果你使用的是 Java、C## 开发语言就需要将私钥转换为 PKCS8 格式),相比 PKCS#1 更加通用,可以表示任意类型的私钥,不仅限于 RSA。

特别说明

如果你的 openssl 版本大于或等于 3.0,那么生成的私钥本身就是 PKCS#8 标准格式,无需再执行格式转换!

命令如下:

$ openssl pkcs8 -topk8 -inform PEM -in private_key.pem -outform PEM -out private_key_pkcs8.pem [-nocrypt]
  • private_key_pkcs8.pem:要输出的 PSCK#8 格式文件
  • -nocrypt:用于指定不对 PKCS#8 文件内容加密(根据实际需要使用,不过通常都会加上该参数)。如果不指定该参数,那么 OpenSSL 会提示你输入一个密码来加密私钥。通常提示信息如下:
Enter Encryption Password:

现在除了私钥公钥之外,当前目录下还多了一个 PKCS8 格式私钥文件:

$ ls
private_key.pem public_key.pem private_key_pkcs8.pem

因为 PEM 格式内容本身就是 Base64 编码的 ASCII 文本。如果程序不想以文件的形式读取 private_key_pkcs8.pem 文件,而是通过字符串的形式使用,只需要删除文件首尾两行并去除换行即可。

DER 和 PEM 格式区别

rsa 密匙对除了 PEM 格式之外,还有 DER 格式。简单地说,他们之间的区别只是内容形式不同。DER 使用二进制编码,文件内容是原始的二进制数据。而 PEM 使用 Base64 编码,文件内容是 Base64 编码的文本。另外:

格式可读性文件扩展名应用场景
DER不可读,需要使用特定的工具才能查看内容通常使用 .der 或 .cer 作为扩展名常用于程序和系统内部使用,因为二进制更高效、更紧凑
PEM可读,可以直接查看文件内容通常使用 .pem 作为扩展名常用于人工查看和编辑,因为它是 Base64 编码的 ASCII 字符,更易读
特别强调

DER 是二进制格式数据,PEM 是 Base64 编码文本数据。

格式转换

PKCS#1 转 DER

openssl rsa -in private_key.pem -outform DER -out private_key.der

PKCS#1 转 PKCS#8

openssl pkcs8 -topk8 -inform PEM -in private_key.pem -outform PEM -out private_key_pkcs8.pem [-nocrypt]

PKCS#8 转 DER

openssl pkcs8 -topk8 -in private_key.pem -outform DER -out private_key_pkcs8.der [-nocrypt]

公钥为什么不需要做格式转换

公钥不需要像私钥那样转换格式的主要原因有以下几点:

公钥格式标准化

公钥通常采用 X.509 标准。即以 “-----BEGIN PUBLIC KEY-----” 开头,“-----END PUBLIC KEY-----” 结尾的 PEM 格式。这已经成为公钥表示的事实标准。

相比私钥,公钥格式更加统一和简单,不需要像私钥那样有多种不同的表示方式。

公钥用途单一

公钥主要用于加密数据和验证数字签名。它不像私钥那样需要用于解密或生成签名等多种操作。因此公钥的格式要求相对简单,无需像私钥那样有多种表示方式来适应不同的应用场景。

不涉及敏感信息

公钥是公开的,不涉及任何敏感信息。因此在使用时不需要像私钥那样需要特殊的安全处理。相比之下,私钥包含了敏感的密钥材料,需要采取更加谨慎的措施进行保护和管理。

扩展

使用Java生成RSA密匙对

Java 程序生成的 RSA 密匙对私钥默认为 PKCS#8 格式,公钥则是 X.509 格式。具体的代码也很简单,直接上码:

public static void main(String[] args) throws Exception {
genKeyPair();
}

public static void genKeyPair() throws Exception {

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");

keyPairGen.initialize(2048); // 密匙长度

KeyPair keyPair = keyPairGen.generateKeyPair();

RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();

System.out.println("私钥格式:" + privateKey.getFormat()); // PKCS#8
System.out.println("公钥格式:" + publicKey.getFormat()); // X.509

System.out.println("私钥内容: \n" + encodeToString(privateKey.getEncoded()));
System.out.println("公钥内容: \n" + encodeToString(publicKey.getEncoded()));
}

private static String encodeToString(byte[] encoded) {
return Base64.getEncoder().encodeToString(encoded);
}

private static byte[] decodeToByte(String decoded) {
return Base64.getDecoder().decode(decoded);
}

使用 Go 生成RSA密匙对

使用 Go 生成 rsa 密匙对需要使用 golang.org/x/crypto 密码库:

go install golang.org/x/crypto

相比较 Java,使用 Go 生成 rsa 密匙对就方便很多,而且将公钥和私钥转换为其他格式也特别简单,下面是示例:

package main

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"os"
)

func main() {
// 生成私钥
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)

// 输出 pkcs1 私钥
pem.Encode(os.Stdout, &pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
})

// 输出 pkcs8 私钥
pkcs8Private, _ := x509.MarshalPKCS8PrivateKey(privateKey)
pem.Encode(os.Stdout, &pem.Block{
Type: "PRIVATE KEY",
Headers: nil,
Bytes: pkcs8Private,
})

// 输出 X.509 公钥
x509Public, _ := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
pem.Encode(os.Stdout, &pem.Block{
Type: "PUBLIC KEY",
Headers: nil,
Bytes: x509Public,
})
}

参考资料

https://www.cryptosys.net/pki/rsakeyformats.html

https://stackoverflow.com/questions/10783366/how-to-generate-pkcs1-rsa-keys-in-pem-format