加解密、签名验证总结

因为做试用期的小功能,实际使用了一下这方面的框架,总结一下。 我选择的方案是用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 Overflowjava - 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);  
    }