加解密、签名验证总结
因为做试用期的小功能,实际使用了一下这方面的框架,总结一下。 我选择的方案是用python加密和签名,在Java端解密和验证。这里有个重要的点就是两方的方案包括参数要对的上。
对称加密(加解密)
比较简单,大部分能找到的代码都是可以直接用的。
我选用的方法是AES,需要注意的地方是我这里symmetric_key是Base64编码的,如果从文件读取,需要先解码。
生成密钥
import base64
import os
def generate_symmetric_key():
return os.urandom(32) # 生成 256-bit 对称密钥
def encode_key_to_base64(key):
return base64.b64encode(key).decode('utf-8')
def save_key_to_file(key, filename):
with open(filename, 'w') as f:
f.write(encode_key_to_base64(key))
key = generate_symmetric_key()
save_key_to_file(key, 'symmetric_key.key')
加密
def encrypt(data: str, symmetric_key: bytes) -> str:
# 生成 12 字节的随机 noncenonce = os.urandom(12)
cipher = AES.new(symmetric_key, AES.MODE_GCM, nonce=nonce)
encrypted_data, tag = cipher.encrypt_and_digest(data.encode())
# 返回 nonce 和加密数据(包括 GCM tag)
return base64.b64encode(nonce + encrypted_data + tag).decode('utf-8')
解密(Java)
public static String decrypt(String data, byte[] symmetricKey) throws Exception {
byte[] encryptedData = Base64.getDecoder().decode(data);
byte[] nonce = new byte[12];
byte[] tag = new byte[16];
byte[] ciphertext = new byte[encryptedData.length - 12];
System.arraycopy(encryptedData, 0, nonce, 0, 12);
System.arraycopy(encryptedData, encryptedData.length - 16, tag, 0, 16);
System.arraycopy(encryptedData, 12, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(symmetricKey, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new GCMParameterSpec(128, nonce));
byte[] decryptedData = cipher.doFinal(ciphertext);
return new String(decryptedData, StandardCharsets.UTF_8);
}
非对称加密(签名验证)
我选用的是RSA-PSS签名方案,具体的过程是使用private_key签名,使用public_key验证。
这里注意的地方是使用cryptography有可能在Java上无法验证成功,我换了pycryptodome才成功。具体可以看Frequently Asked Questions — PyCryptodome 3.210b0 documentation ,在用pycryptodome时,使用的命名空间是Crypto,可能与其他的库发生冲突,需要先卸载。
另外一个问题是在android中的,在java - BouncyCastleProvider not picked for Signature object - Stack Overflow和java - Change “salt length” parameter in PSS parameters for Bouncycastle - Stack Overflow提到的问题,Android可能需要在加载pss参数之前就init.
同样的,我们在验证的时候也要注意输入的data是原始的未hash过的数据,签名的数据和公钥都是Base64编码的,要注意解码。
生成密钥
from Crypto.PublicKey import RSA
def generate_rsa_key_pair(key_size=2048):
# 生成 RSA 密钥对
key = RSA.generate(key_size)
private_key = key.export_key() # 导出私钥
public_key = key.publickey().export_key() # 导出公钥
return private_key, public_key
def save_key_to_pem(key: bytes, file_path: str):
with open(file_path, 'wb') as file:
file.write(key)
# 生成密钥对
private_key, public_key = generate_rsa_key_pair()
# 保存私钥和公钥到文件
save_key_to_pem(private_key, 'private_key.pem')
save_key_to_pem(public_key, 'public_key.pem')
print("RSA keys have been generated and saved to 'private_key.pem' and 'public_key.pem'.")
签名
def sign(data: str, private_key) -> str:
# 导入私钥
key = private_key
# 创建 SHA256 哈希对象
h = SHA256.new(data.encode())
# 使用 PSS 填充进行签名
signer = pss.new(key, salt_bytes=32)
signature = signer.sign(h)
# 使用 Base64 编码
return base64.b64encode(signature).decode('utf-8')
验证(Java)
public static boolean verifySignature(String data, String signatureBase64, PublicKey publicKey) throws Exception {
Signature verifier = Signature.getInstance("SHA256withRSA/PSS");
verifier.initVerify(publicKey);
PSSParameterSpec pssParams = new PSSParameterSpec(
"SHA-256", // Hash algorithm
"MGF1", // MGF algorithm
new MGF1ParameterSpec("SHA-256"), // MGF algorithm parameter
32, // Salt length
1
);
verifier.setParameter(pssParams);
verifier.update(data.getBytes(StandardCharsets.UTF_8));
byte[] signature = Base64.getDecoder().decode(signatureBase64);
return verifier.verify(signature);
}