标题:PYTHON RSA加解密 出处:Felix021 时间:Sun, 22 Mar 2020 00:31:16 +0000 作者:felix021 地址:https://www.felix021.com/blog/read.php?2212 内容: 和某厂通信需要按其要求用rsa加密某段数据,该厂给了个Example.java,和一个X509 Certificate。 之前是把java编译好,然后在python里用system来调用它,有点丑。 最近需要复用这段代码,希望代码干净点,所以在Python里重新实现一遍。 # 1. 将 x509cer 转成 PEM 格式 import java.io.*; import java.util.Base64; import java.security.PublicKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; public class Test2 { public static PublicKey getPublicKeyByX509Cer(String cerFilePath) throws Exception { InputStream x509Is = new FileInputStream(cerFilePath); CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)certificatefactory.generateCertificate(x509Is); x509Is.close(); return cert.getPublicKey(); } public static void main(String[] args) throws Exception { PublicKey pubKey = getPublicKeyByX509Cer("public.cer"); System.out.println(Base64.getEncoder().encodeToString(pubKey.getEncoded())); } } 输出PEM编码的公钥,类似:MIGfMA0GCSqGSIb3DQ...(中间省略)...rAvxiOfQIDAQAB # 2. 在Python中加密 #!/usr/bin/python #coding:utf-8 import base64 import binascii import Crypto from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQ...(中间省略)...rAvxiOfQIDAQAB" #返回base64编码后的密文 def RSAEncrypt(message, pubkey): binary = base64.b64decode(pubkey) key = RSA.importKey(binary) #PKCS#1建议的padding需要占用11个字节 length = (key.size() + 1) / 8 - 11 cipher = PKCS1_v1_5.new(key) res = [] for i in range(0, len(message), length): res.append(cipher.encrypt(message[i:i+length])) result = ''.join(res) return binascii.hexlify(result) print RSAEncrypt("123456", PUBLIC_KEY) 由于该厂在Java代码中使用 "RSA/NONE/PKCS1Padding",和PKCS1_v1_5的默认padding一致,因此不需要特殊处理。 注: 如果希望使用 NoPadding,看起来Python的Crypto没有直接提供支持,可能需要用PKCS1_OAEP并自己填充ASCII 0(但不确定,好像OAEP也包含了某种padding,encrypt的文档里说"长度需要 - 2 - 2倍hashcode长度"),几年前用PHP的时候踩过坑,详见 https://www.felix021.com/blog/read.php?2169 RSA: Java bouncy castle 与 PHP openssl_public_encrypt 兼容的那点事儿 # 3. 解密 反向处理就好,先base64 decode,分段解密再拼起来 PRIVATE_KEY = "MIICXQIBAAKBgQ....." # 输入是base64编码的密文 def RSADecrypt(message, prikey): binary = base64.b64decode(prikey) key = RSA.importKey(binary) length = (key.size() + 1) / 8 cipher = PKCS1_v1_5.new(key) message = binascii.unhexlify(message) res = [] for i in range(0, len(message), length): res.append(cipher.decrypt(message[i:i+length], 'sentinel: random message')) result = ''.join(res) return result 注:decrypt方法的文档里说,"sentinel" 应当是一个无意义的字符串,并且尽量应当和典型的明文长度相似(这里偷懒了)。因为PKCS1_v1_5没有完善的完整性校验,某个构造的输入可能可以被正确解码,虽然看起来是一串没有意义的随机文字。加上sentinel以后,当解密出错的时候,会返回指定的 sentinel 继续后续处理流程,从而可以躲避选择密文攻击的检测。 关于这个选择密文攻击的细节可参考这篇文章:SSL.TLS协议安全系列:SSL的Padding Oracle攻击 # 4. 其他 可以用这个命令来生成RSA的公私钥对 引用 $ openssl genrsa -out test.pem 1024 $ openssl rsa -in test.pem -pubout -out test_public.pem RSA.importKey(key) 方法的 key 也可以直接使用 PEM 文件的内容,也可以用 base64 decode 以后的 binary data。 Generated by Bo-blog 2.1.0