本来用 rsa 库基本搞定,但文心一言建议改用 ring 库。原因是 rsa 库已经放弃维护,而 ring 库性能公认很好。但是如何进行 RSA 数字签名,网上几乎查不到这方面材料。仔细查看了 ring 库的源代码和代码注释,终于完成趟坑。总结一下供大家参考。
1. 生成密钥文件
用 RSA数字签名算法时,如何从磁盘文件加载密钥?这需要用 openssl 工具。这个工具如何下载安装不再赘述。
1.1 PEM 格式密钥文件
PEM(Privacy-Enhanced Mail)直译为“保密邮件”,但在此上下文中,它主要指的是一种用于存储加密密钥和证书的文件格式。PEM格式是一种基于ASCII编码的密钥和证书的存储格式,说白了就是文本文件,广泛用于安全领域,特别是在SSL/TLS协议中的证书和密钥管理。
以下是关于PEM格式密钥文件的详细解释:
-
文件内容:
- PEM文件可以包含公钥、私钥、证书等敏感信息。
- 这些信息通常以Base64编码的形式存储,并且包含了起始标记和结束标记,以便于识别和区分不同类型的密钥和证书。
-
文件结构:
- PEM文件的内容通常以
-----BEGIN <标签>-----
开头,以-----END <标签>-----
结束。 <标签>
部分用于指示文件内容的类型,例如CERTIFICATE
表示证书,PRIVATE KEY
表示私钥,等等。- Base64编码的数据位于这两个标记之间,是实际的密钥或证书内容。
- PEM文件的内容通常以
-
应用场景:
- PEM格式的文件在建立安全的网络连接(如HTTPS)时用于证明服务器的身份。
- 它们也用于对数据进行加密和解密,或者进行数字签名。
- PEM文件还可以用于构建完整的证书链,以验证服务器证书的有效性。
-
安全性:
- 由于PEM文件中包含了敏感的密钥和证书信息,因此需要采取一些措施来保护其安全性。
- 可以通过严格限制访问权限、使用加密算法进行加密存储、以及采取安全通道和安全设备来传输和存储PEM文件,以防止中间人攻击和数据泄露。
-
与其他格式的转换:
- PEM格式与其他格式(如DER、PKCS#7、PKCS#12等)之间的转换是常见的需求。
- 可以使用OpenSSL工具或其他相关工具来完成这些转换。
综上所述,PEM是一种重要的密钥和证书存储格式,在安全通信和数据保护方面发挥着关键作用。了解PEM格式的结构和应用场景,有助于更好地管理和保护敏感信息的安全性。
1.2 生成 PEM 格式的 RSA 密钥文件
下面 openssl 命令可以生成一个名为private_key.pem
的密钥文件。该文件可以用记事本打开查看结果。
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
1.3 从 RSA 密钥文件导出 RSA 公钥文件
下面 openssl 命令可以从私钥文件 private_key.pem
中提取出公钥文件 public_key.pem
。
openssl rsa -pubout -in private_key.pem -out public_key.pem
1.4 PEM 密钥文件的使用
我曾用过 C++ 的 Crypto 库,这个库可以直接使用 PEM 密钥文件加载密钥。我以为 ring 库也是如此,结果非常失望。如果哪位朋友能用 ring 库直接加载,希望能告知,本人不胜感谢!
1.5 生成 PKCS#8 密钥
私钥文件用于签名。ring 库可以导入PKCS#8 规格的密钥,要求密钥文件按照 pk8
格式保存。ring 库的源代码注释提供了数字签名的方法。首先 ring 库需要的 PKCS#8 密钥证书的生成方法,代码如下:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 | openssl pkcs8 -topk8 -nocrypt -outform der > rsa-2048-private-key.pk8
1.6 导出公钥文件
公钥文件用于验证数字签名。ring 库可以读取 DER 格式的公钥文件。但 ring 的源代码等文档并没提供如何获得这份文件。经过反复尝试,发现从私钥文件抽取公钥文件分两步进行:首先生成 PEM 格式公钥文件,然后再转换成 DER 格式的公钥文件。命令如下:
openssl rsa -pubout -in rsa-2048-private-key.pk8 -inform DER -outform PEM -out rsa-2048-public-key.pem
openssl rsa -pubin -in rsa-2048-public-key.pem -inform PEM -RSAPublicKey_out -outform DER -out rsa-2048-public-key.der
1.7 关于 PEM、DER、PK8 格式的说明
PEM、DER、PK8(通常指的是PKCS#8私钥格式,但为简化讨论,这里将PK8视为PKCS#8的私钥部分的简称)是三种常见的密钥文件格式,它们各自有不同的特点和应用场景。以下是关于这三种密钥文件格式以及它们名字来历的详细说明:
1.7.1 PEM(Privacy-Enhanced Mail)
- 来历:
- PEM格式最早由美国互联网工程任务组(IETF)在1987年的RFC 1421中定义。
- 最初是为了解决电子邮件的加密和签名问题而设计的。
- 特点:
- PEM格式是一种基于ASCII编码的密钥和证书的存储格式。
- 文件通常以“.pem”为后缀名。
- 可以包含公钥、私钥、证书等敏感信息。
- 使用Base64编码,并且包含了起始标记和结束标记,以便于识别和区分不同类型的密钥和证书。
- 具有良好的可读性和可编辑性。
1.7.2 DER(Distinguished Encoding Rules)
- 来历:
- DER是一种二进制编码格式,常用于证书和密钥的存储和传输。
- 它是ASN.1(Abstract Syntax Notation One)标准的一部分,由国际电信联盟(ITU-T)定义。
- 特点:
- DER文件通常以“.der”或“.cer”(在某些上下文中,特别是Windows平台上,“.cer”可能用于表示DER编码的证书文件)为后缀名。
- 与PEM格式相比,DER格式更加紧凑和高效,因为它使用二进制编码而不是Base64编码。
- DER格式的文件不易于阅读和编辑,通常需要专业的工具才能查看和解析。
1.7.3 PKCS#8(Private-Key Cryptography Standards #8)
- 来历:
- PKCS#8是由RSA实验室发布的一种标准,用于存储私钥信息。
- 它是PKCS(Public-Key Cryptography Standards)系列标准的一部分。
- 特点:
- PKCS#8定义了私钥的语法,包括如何对私钥进行编码和加密。
- 提供了未加密和加密两种格式的私钥存储方式。
- 未加密的私钥信息通常以明文形式存储(但编码为Base64或其他格式以方便存储和传输),而加密的私钥信息则使用密码短语或其他加密算法进行保护。
- 常与OpenSSL等工具配合使用。
- 在较新版本的OpenSSL中,生成的RSA私钥默认符合PKCS#8的PrivateKeyInfo结构。
需要注意的是,虽然PEM、DER和PKCS#8在密钥和证书的存储和管理中扮演着重要角色,但它们并不是互斥的。实际上,它们可以相互转换,并且可以在不同的应用场景中共同使用。例如,可以使用OpenSSL等工具将PEM格式的私钥转换为DER格式,或者将PKCS#8格式的私钥转换为PKCS#1格式的私钥(尽管PKCS#1主要用于公钥的存储)。
2. 实现签名与验证的源代码
下面直接贴 ring 提供的示例代码:
Cargo.toml 添加下面库:
ring = "0.17.8"
程序源代码:
use ring::{rand, rsa, signature};fn sign_and_verify_rsa(private_key_path: &std::path::Path,public_key_path: &std::path::Path)-> Result<(), MyError> {
// Create an RSA keypair from the DER-encoded bytes. This example uses
// a 2048-bit key, but larger keys are also supported.
let private_key_der = read_file(private_key_path)?;
let key_pair = rsa::KeyPair::from_der(&private_key_der).map_err(|_| MyError::BadPrivateKey)?;// Sign the message "hello, world", using PKCS#1 v1.5 padding and the
// SHA256 digest algorithm.
const MESSAGE: &'static [u8] = b"hello, world";
let rng = rand::SystemRandom::new();
let mut signature = vec![0; key_pair.public().modulus_len()];
key_pair.sign(&signature::RSA_PKCS1_SHA256, &rng, MESSAGE, &mut signature).map_err(|_| MyError::OOM)?;// Verify the signature.
let public_key =signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256,read_file(public_key_path)?);
public_key.verify(MESSAGE, &signature).map_err(|_| MyError::BadSignature)
}#[derive(Debug)]
enum MyError {IO(std::io::Error),BadPrivateKey,OOM,BadSignature,
}fn read_file(path: &std::path::Path) -> Result<Vec<u8>, MyError> {use std::io::Read;let mut file = std::fs::File::open(path).map_err(|e| MyError::IO(e))?;let mut contents: Vec<u8> = Vec::new();file.read_to_end(&mut contents).map_err(|e| MyError::IO(e))?;Ok(contents)
}
我加了一段测试程序供参考:
#[test]
fn test2() {let pkcs8 = read_file(std::path::Path::new("rsa-2048-private-key.pk8")).unwrap();let key_pair = rsa::KeyPair::from_pkcs8(&pkcs8).unwrap();const MESSAGE: &'static [u8] = b"hello, world";let rng = rand::SystemRandom::new();let mut signature = vec![0; key_pair.public().modulus_len()];key_pair.sign(&signature::RSA_PKCS1_SHA256, &rng, MESSAGE, &mut signature).map_err(|_| MyError::OOM).unwrap();println!("\nsignature:{:?}", signature);let public_key = signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256,read_file(std::path::Path::new("rsa-2048-public-key.der")).unwrap(),);println!("\npublic_key = {:?}", public_key);public_key.verify(MESSAGE, &signature).unwrap();
}
3. 心得体会
- 阅读开源库的源代码可以解决文档不足的问题。
- 希望多展开交流,加快学习进步!