Python 与 C# 的 RSA 加解密数据交互

栏目: Python · 发布时间: 6年前

内容简介:Python 与 C# 的 RSA 加解密数据交互

最近在做一个东西,服务端是用 Python 写的 API,客户端是其他语言写的,比如 C#。客户端与服务端之间有个数据通信的需求是通过 RSA 做加解密。例如客户端 C# 使用公钥加密数据,服务端 Python 使用私钥解密数据。因此需要让不同语言的 RSA 加密后的数据兼容,能够互相解密。

在 C# 中 RSA 的私钥和公钥文件是使用 XML 格式存储的,而 Python 的 pycrypto 是使用 PEM 格式,第一个要解决的问题是密钥文件的一致性问题。使用 openssl 生成的 RSA 密钥文件,C# 无法读取。研究了很久 C# 读取 PEM 的方法,但是没有找到简单高效的方法,最后的妥协方法是使用 C# 生成 RSA 的密钥,然后再转成 PEM 格式。

生成 xml 格式的 RSA 密钥

使用如下代码,用 C# 生成 RSA 的密钥

public void GenerateRSAKey()
{
    using (var rsa = new RSACryptoServiceProvider(2048))
    {
        // 输出私钥
        Console.WriteLine(rsa.ToXmlString(true));
        // 输出公钥
        Console.WriteLine(rsa.ToXmlString(false));
    }
}

将 RSA 由 xml 转成 pem 格式

可以使用 这个在线工具 ,但是在线 工具 毕竟有数据泄露的风险,最后参考了 这篇文章 ,代码在这里 XMLSec2PEM.java

编译代码

javac XMLSec2PEM.java

转换 XML 到 PEM

java XMLSec2PEM your_key.xml

将上述输出的内容保存到 rsa_key.pem。在 Python 中调用会提示 不支持的 RSA Key 格式 ,需要进一步做转换

openssl pkcs8 -topk8 -in rsa_key.pem -nocrypt -out rsa.key

最后得到可用的 rsa.key,使用 openssl 提取出公钥

openssl rsa -in rsa.key -pubout -out rsa_pub.key

不同语言兼容的 RSA 加解密

要做到 C# 和 Python 相互兼容的 RSA 加解密还是有很多细节问题的,例如 C# 默认会做随机的填充,因此每次加密得到的数据都不同,以及 Unicode 和 UTF8的编码问题等。

关于 C# 和 Python 之间相互加解密,可以参考 这篇文章代码在这 。以下代码经过测试,C# 加密的数据,Python 可以正确解密,反之亦然。

Python 版的完整代码

需要先安装依赖

pip install pycrypto
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import
import sys
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3

if PY3:
    text_type = str
    binary_type = bytes
else:
    text_type = unicode
    binary_type = str


def utf8(value):
    if not isinstance(value, binary_type) and not isinstance(value, text_type):
        value = binary_type(value)
    if isinstance(value, text_type):
        return value.encode('utf-8')
    else:
        return value


_TO_UNICODE_TYPES = (text_type, type(None))


def to_unicode(value):
    if isinstance(value, _TO_UNICODE_TYPES):
        return value
    if not isinstance(value, bytes):
        raise TypeError(
            "Expected bytes, unicode, or None; got %r" % type(value)
        )
    return value.decode("utf-8")


def read_file(file_path):
    with open(file_path, 'r') as f:
        return f.read()


rsa_key_content = read_file('rsa.key')
rsa_pub_key_content = read_file('rsa_pub.key')


class RSAHelper(object):
 @classmethod
    def decrypt(cls, enc_data, key_content=rsa_key_content, passphrase=''):
        rsa = RSA.importKey(key_content, passphrase=passphrase)
        # Generate a cypher using the PKCS1.5 standard
        cipher = PKCS1_v1_5.new(rsa)
        return cipher.decrypt(enc_data, '')

 @classmethod
    def encrypt(cls, plain_data, key_content=rsa_pub_key_content, passphrase=''):
        rsa = RSA.importKey(key_content, passphrase=passphrase)
        # Generate a cypher using the PKCS1.5 standard
        cipher = PKCS1_v1_5.new(rsa)
        return cipher.encrypt(utf8(plain_data))

 @classmethod
    def encrypt_b64(cls, plain_data, key_content=rsa_pub_key_content, passphrase=''):
        data = cls.encrypt(plain_data, key_content, passphrase)
        data = to_unicode(base64.b64encode(utf8(data)))
        return data

 @classmethod
    def decrypt_b64(cls, enc_data, key_content=rsa_key_content, passphrase=''):
        enc_data = base64.b64decode(utf8(enc_data))
        data = cls.decrypt(enc_data, key_content, passphrase)
        data = to_unicode(data)
        return data


if __name__ == '__main__':
    enc = RSAHelper.encrypt_b64('123456中文')
    print(enc)
    plain = RSAHelper.decrypt_b64(enc)
    print(plain)

C# 版的完整代码

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace TestConsoleApplication
{
    public class RSAHelper
    {
        public static void Test()
        {
            try
            {
                var privateKey = ReadKeyFile("rsa.xml");
                var publicKey = ReadKeyFile("rsa_pub.xml");
                var dataToEncrypt = "123456中文";
                var encryptedData = EncryptBase64(dataToEncrypt, publicKey);
                Console.WriteLine("加密后:\n{0}", encryptedData);
                var decryptedData = DecryptBase64(encryptedData, privateKey);
                Console.WriteLine("解密后:\n{0}", decryptedData);
            }
            catch (ArgumentNullException)
            {
                Console.WriteLine("Encryption failed.");
            }

            Console.ReadLine();
        }

        public static string ReadKeyFile(stringfilePath)
        {
            using (var fs = new FileStream(filePath, FileMode.Open))
            {
                var certBytes = new byte[fs.Length];
                fs.Read(certBytes, 0, (int)fs.Length);
                return Encoding.UTF8.GetString(certBytes);
            }
        }

        public static string EncryptBase64(stringdataToEncrypt,stringkeyContent,booldoOAEPPadding =false)
        {
            var byteConverter = new UTF8Encoding();
            var bytesToEncrypt = byteConverter.GetBytes(dataToEncrypt);
            var data = Encrypt(bytesToEncrypt, keyContent, doOAEPPadding);
            if (data == null)
            {
                return null;
            }

            var b64Data = Convert.ToBase64String(data);
            return b64Data;
        }

        public static string DecryptBase64(stringdataToDecrypt,stringkeyContent,booldoOAEPPadding =false)
        {
            var bytesToEncrypt = Convert.FromBase64String(dataToDecrypt);
            var data = Decrypt(bytesToEncrypt, keyContent, doOAEPPadding);
            var byteConverter = new UTF8Encoding();
            return byteConverter.GetString(data);
        }

        public static byte[]Encrypt(byte[] dataToEncrypt,stringkeyContent,booldoOAEPPadding =false)
        {
            try
            {
                byte[] encryptedData;
                using (var rsa = new RSACryptoServiceProvider())
                {
                    rsa.FromXmlString(keyContent);
                    encryptedData = rsa.Encrypt(dataToEncrypt, doOAEPPadding);
                }
                return encryptedData;
            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);
                return null;
            }
        }

        public static byte[]Decrypt(byte[] dataToDecrypt,stringkeyContent,booldoOAEPPadding =false)
        {
            try
            {
                byte[] decryptedData;
                using (var rsa = new RSACryptoServiceProvider())
                {
                    rsa.FromXmlString(keyContent);
                    decryptedData = rsa.Decrypt(dataToDecrypt, doOAEPPadding);
                }
                return decryptedData;
            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.ToString());
                return null;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            RSAHelper.Test();
        }
    }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

再启动

再启动

[日] 大前研一 / 田龙姬、金枫 / 中华工商联合出版社有限责任公司 / 2010-1 / 29.00元

1、“全球管理大师”、“日本战略之父”大前研一,职场励志最新巨作。 2、2010年1月中华工商联合出版社有限责任公司与日知公司继《货币战争2》《中国大趋势》之后,再度联手,重磅推出。 3、震撼中国职场的宗师级巨作,势必引领2010年中国职场4、世界著名出版商小学馆授予独家中文简体出版权。 5、试问,哪个老板不希望自己的员工不断实现自身的“再启动”呢? 6、只有不断激励鞭策自......一起来看看 《再启动》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具