前后端跨语言RSA加解密和签名验证实现(js+python)

信息安全课程作业,敲了整整4天才基本搞定,还有一小问题没解决,可以的话评论区留言感激不尽。

总体思路:

该系统后端使用python的tornado框架(专门实现聊天功能的框架,笔者也只学了一天),前端使用vue框架(其实原生html也可以,我是觉得vue更方便管理代码)

前端js(客户端)使用RSA加密,即务端公钥加密,加密对象为m|H(m),即得到PUb[m|H(m)];后端python使用客户端私钥解密m和H(m),后端再对m生成消息摘要,通过判断消息摘要和H(m)是否相同来判断消息是否被篡改。此时实现了保密性和完整性。

前端再用客户端私钥对m生成签名sig(m),后端使用客户端公钥验证签名合法性,此时保障了不可抵赖性。

严格来说,前端加密后的消息应为PUb{m|sig[H(m)]},但我发送的消息为PUb[m|H(m)] | sig(m),原因暂且按下不表,下面会解释。

系统难点

如果只用js加解密和验签,或者只用python加解密验签,会很容易。这很好理解,毕竟同一语言中使用同一库不会出现不兼容的额问题。所以在不同语言中,前端和后端需要找到相互兼容的库,前端加密后的信息,后端需要解密得出来;前端签名后端也得能验签!

然后笔者发现python的Crypto库有点儿东西,加解密和签名验签全都能实现,还支持很多种相关算法,宝藏!!同时,前端的jsencrypt库和jsrsasign库是笔者找了好久,发现能和python的Crypto库兼容的加解密库和签名库。本次系统使用cdn引入,位置如下。

//MD5算法库
<script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js"></script>
//加密库(本次用RSA算法加密)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/3.2.1/jsencrypt.min.js"></script>
//签名库
<script src="https://cdn.bootcdn.net/ajax/libs/jsrsasign/10.5.13/jsrsasign-all-min.min.js"></script>

python的库也有点儿多,其中PKCS1_v1_5 笔者还不是很清楚,欢迎读者在评论区帮忙补充嘻嘻

from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
import Crypto.Signature.PKCS1_v1_5 as sign_PKCS1_v1_5  # 用于签名/验签
from Crypto.PublicKey import RSA
import base64
from Crypto.Hash import MD5

密钥生成

 在线RSA密钥对生成工具 - UU在线工具 (uutool.cn)

生成的密钥格式如下,将其保存为pem格式文件以便程序读取,一共会生成4个pem:前后端的公私钥。

-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDmaZl9cf9Z6RhGeLL1fgI4HuRPoS3IfkQF81Vlxf6NLrII5XEIWDyLHUyAiKpbOWdeup6Ra8btwTfMO5Jqa3eG4wKNKWoFNboTBtutriq9QRdfj3BQJjNieIAYN9Mykfxqkqh9+lEvjcm5MULeZRPkT4TjroEDiftegm2AYOOyPwIDAQAB-----END PUBLIC KEY-----

 前端加密和签名

初始化变量,其实不需要下面这么多,按自己需要来就行。

// 服务端的公钥
publicKey_server: "-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDmaZl9cf9Z6RhGeLL1fgI4HuRPoS3IfkQF81Vlxf6NLrII5XEIWDyLHUyAiKpbOWdeup6Ra8btwTfMO5Jqa3eG4wKNKWoFNboTBtutriq9QRdfj3BQJjNieIAYN9Mykfxqkqh9+lEvjcm5MULeZRPkT4TjroEDiftegm2AYOOyPwIDAQAB-----END PUBLIC KEY-----",
// 服务端私钥
privateKey_server: "-----BEGIN RSA PRIVATE KEY-----MIICXgIBAAKBgQDmaZl9cf9Z6RhGeLL1fgI4HuRPoS3IfkQF81Vlxf6NLrII5XEIWDyLHUyAiKpbOWdeup6Ra8btwTfMO5Jqa3eG4wKNKWoFNboTBtutriq9QRdfj3BQJjNieIAYN9Mykfxqkqh9+lEvjcm5MULeZRPkT4TjroEDiftegm2AYOOyPwIDAQABAoGAbVo9yf8R+Rp69msvR/qLVBY5Nh+hSm++mfJgG8Kpqli4jydRi2vRJBb+KVxzOYNXb2pzekHj8g/LCwdU2GPzn+7R5sqEegf1oL+om7XQM7Ny4dXvqWhBvaiA1I0Bsj9m2MevOGRH9AtQTa3hUlbYvmP6toi2Tg8ewXfmMTlHodUCQQDyccQ8YaphQkKdy/WTt7CQfEYHVjaGoURkYg0ZauXHqNc0sngHd5rzKMLtOUBvEjHuy1/k01Ra7q2VV2uAwvvrAkEA80udNsSrkYLKv9/qwDjrwx2ibJqE4o3d+g4ob/22OB4ZnJqgRrVhM6pXPbqTsSHlochQMvlBXA5R7nnUw3px/QJBAMcqanjoCp2nXy5eNTnKdwPa83RngJeMt7B3VCeDR4yDyXcC/dO0j9gdrjRPCf20xsxSyk4ixXOGC5dZn3jBtU0CQQCAj9lQopZiuvF2eNVso+d5YER/DRvhN8QvqaGWpEPQ3Z79EPxWwOvPSFj3Zos608WrOtWeSfZOtcZ3tOtILIDlAkEA3iA8eaJto/aRSzpS1Q/30G7d+gv85yWk1dQD1y6hRVB9BQkbVw3mrjkn21UEWXaspzSXkeTzMAuVzQiu4UQUbg==-----END RSA PRIVATE KEY-----",
//客户端公钥
publicKey_client: "-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOViauny7DuRhO5GgtMC1kOizaQrBGRv6NrbhuIS+YoxMuf1mma1dBOZ7gd5fEDAxW2/S6jYzVI4vi33w8Z9K//HTMXnyBmerzRKEPFrP55YijltoE6eIOYCo0iuuG00ZvIS/QgXonrIZ/P4Qn4Nip89uMMo9G6EdW5vswyowvwQIDAQAB-----END PUBLIC KEY-----",
// 客户端私钥
privateKey_client: "-----BEGIN RSA PRIVATE KEY-----MIICWwIBAAKBgQDOViauny7DuRhO5GgtMC1kOizaQrBGRv6NrbhuIS+YoxMuf1mma1dBOZ7gd5fEDAxW2/S6jYzVI4vi33w8Z9K//HTMXnyBmerzRKEPFrP55YijltoE6eIOYCo0iuuG00ZvIS/QgXonrIZ/P4Qn4Nip89uMMo9G6EdW5vswyowvwQIDAQABAoGAOGOt/aIOPzoaDRF58QOOHCqT8HAySXqEfcbAtQRHxDCpANeX8hW6yl4Lb+/vH4plYuWI2+TsXKFyzOVjyARdwVDaaSz9Nod5xfgxfshiXbkWQ8lF6jnx0KY/gTP3xowN9iMtWaZ6bapqZ8b9lU1wxx/sAaJMpU5JhzRI8XaNtDsCQQDPMIAKoGIgf10IcEnle51BPtw4DWyyWH6+38eZ9i2jZklwAOLNlVc8W/B1mqQTn50c6MKlZjkx4KCLSzavjf9HAkEA/vI2HC6S01tB7wsAJ2CJ7kNn4iioXtCRm9fATNQuOx/QX5yW0PVFWF/BMDfozDbKd0Roidjz1AxB4oG8EMGstwJAAWQZ9/hLsFwqi7v1Qw0paR6668VrTWc6sp1eAbKda9Nr+syGuUqfY1BatO9s2pTfwSnu5J1jFOqlKUo/+73AUQJAd34VCH53yOKD88NnLg2ceHVVcnX1/IKrTK0B78CfbozJwJaHRac/+lzfEneSAG1J1j7U9I8gMWoRU1XVTNFJ3wJAV3CWz7UQFfnrRsEX61Tb11G+tBBNWwC5WlUDR3Cji6tZPw4naUq6xS/4Jd4dTJmIrtEbPszepw4AzSZMPeMFSg==-----END RSA PRIVATE KEY-----",


encrypt_client_public: "",
decrypt_client_public: "",
encrypt_client_private: "",
decrypt_client_private: "",
encrypt_server_public: "",
decrypt_server_public: "",
decrypt_server_private: "",

this.encrypt_client_public = new JSEncrypt(),  // 实例化对象
this.decrypt_client_public = new JSEncrypt(),  // 实例化对象
this.encrypt_client_private = new JSEncrypt(),  // 实例化对象
this.decrypt_client_private = new JSEncrypt(), // 实例化对象

this.encrypt_server_public = new JSEncrypt(),  // 实例化对象
this.decrypt_server_public = new JSEncrypt(),  // 实例化对象
this.encrypt_server_private = new JSEncrypt(),  // 实例化对象
this.decrypt_server_private = new JSEncrypt(), // 实例化对象
//客户端
this.encrypt_client_public.setPublicKey(this.publicKey_client),  // 设置加密公钥
this.decrypt_client_public.setPublicKey(this.publicKey_client),  // 设置解密公钥
this.encrypt_client_private.setPrivateKey(this.privateKey_client),  // 设置加密私钥
this.decrypt_client_private.setPrivateKey(this.privateKey_client)  // 设置解密私钥
//服务器
this.encrypt_server_public.setPublicKey(this.publicKey_server),  // 设置加密公钥
this.decrypt_server_public.setPublicKey(this.publicKey_server),  // 设置解密公钥
this.encrypt_server_private.setPrivateKey(this.privateKey_server),  // 设置加密私钥
this.decrypt_server_private.setPrivateKey(this.privateKey_server)  // 设置解密私钥       




//客户端私钥签名函数
sign_c: function (plaintext) {
    signature1 = this.encrypt_client_private.sign(plaintext, CryptoJS.MD5, "md5");  //客户端私钥加签
    return signature1
            },
//客户端公钥验签函数(本系统中无需使用该函数)
verify_c: function (plaintext, signature) {
       this.verified = this.decrypt_client_public.verify(plaintext, signature, CryptoJS.MD5); //客户端公钥验签
        return this.verified
            },        

注意:笔者是用的vue框架,密钥的初始化在不同区域:一个在data函数中返回,一个在created周期中。

 初始化完成后,就可以调用服务器公钥的加密函数了,即:

send: function () {
console.log("服务端公钥加密↓");
msg_md5 = md5(msg.value);
temp = msg.value + "|" + msg_md5
console.log("temp:", temp);
message1 = this.encrypt_server_public.encrypt(temp);
console.log("message1:", message1);
console.log("客户端私钥签名↓");
message2 = this.sign_c(msg.value)
console.log("message2:", message2);
ws.send(message1 + "|" + message2)//发送到后端,PUb(m|H(m))|sign(m)
this.$message({ //ElementUI的消息提示功能
message: '发送成功',
   type: 'success'
   });
msg.value = '' //将输入框中的值置空
   },

 

至此,前端实现了消息加密和签名。

后端解密和验签

不同于前端,后端的秘钥不是直接定义在程序代码里,而是通过读取pem文件拿到。ps:下面的秘钥也不需要完全用到,毕竟服务端怎么可以拿到客户端的私钥,写上去只是方便调试。


# 加载服务器公钥文件
with open('public_server.pem', 'r') as f:
    public_server_key = f.read()
rsakey1 = RSA.importKey(public_server_key)
public_server = Cipher_pkcs1_v1_5.new(rsakey1)

# 加载服务器私钥文件
with open('private_server.pem', 'r') as f:
    private_server_key = f.read()
rsakey3 = RSA.importKey(private_server_key)
private_server = Cipher_pkcs1_v1_5.new(rsakey3)

# 加载客户端公钥文件
with open('public_client.pem', 'r') as f:
    public_client_key = f.read()
rsakey2 = RSA.importKey(public_client_key)
public_client = rsakey2 //注意,这里没有调用Cipher_pkcs1_v1_5方法

# 加载客户端私钥文件
with open('private_client.pem', 'r') as f:
    private_client_key = f.read()
rsakey4 = RSA.importKey(private_client_key)
private_client = Cipher_pkcs1_v1_5.new(rsakey4)

初始化好秘钥过后 ,就可以对前端拿到的消息解密了,解密很简单,一行代码就可以搞定,甚至不需要封装函数。其中的“|”就是字符串“|”,笔者用“|”把他们串联在一起了。这也导致一个bug,如果明文消息中自带字符串“|”,那么消息认证和签名验证都会失败。不过小问题。

//前端发送的消息格式为PUb[m|H(m)]|sig(m)
msg1 = message.split("|", 1)[0] //通过分割|得到PUb[m|H(m)],再用自己的私钥解密
msg1 = private_server.decrypt(base64.b64decode(msg1), None).decode()
print("解密msg1得到:",msg1)

 此时再对msg1进行分割,得到m和H(m),后端通过生成H(m)与前端发来的H(m)对比就可以判断完整性了。

//参数依次为签名,原文,公钥
def to_verify(signature, plain_text, public_key):
    # 验签
    verifier = sign_PKCS1_v1_5.new(public_key)
    _rand_hash = MD5.new()
    _rand_hash.update(plain_text.encode())
    verify = verifier.verify(_rand_hash, signature)
    return verify  # true / false


msg11 = msg1.split("|", 1)[0] //m
msg12 = msg1.split("|", 1)[1] //H(m)

sign = message.split("|", 1)[1] //sig(m)部分
print("sign:",sign)
sign = base64.b64decode(sign)

verify = to_verify(sign, msg11, public_client)
if(verify):
    print("签名安全")

至此,后端验证成功

最后的疑问

既然既能实现签名,也能实现加密,为什么该系统中不能向后端发送PUb{m|sig[H(m)]}呢?

因为签名得到的字符串很长!!!!长到服务端公钥无法加密!!!

console.log("客户端私钥签名↓");
message2 = this.sign_c(msg.value) //生成m的签名
temp = msg.value + "|" + message2
console.log("原文|签名:",temp);
console.log("服务端公钥加密↓");
message = this.encrypt_server_public.encrypt(temp);
console.log("公钥加密:",message);

其是也找到了解决办法,就是js使用encryptlong库加密,这个库可以加密超长字符,而且和encrypt库兼容,但是我找了半天没找到这个库的cdn地址,只有npm安装方法(encryptlong - npm (npmjs.com)),可是我是html静态文件啊,就算安装了html也不能引入本地js文件,最后也就不了了之了。但凡开发者上点心整个cdn地址就好了呜呜呜。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,我可以为您提供一些关于使用openssl编写RSA加解密算法和RSA签名验证算法的基本思路: 1. 引入openssl库 在代码文件中引入openssl库头文件,如下所示: ```c #include <openssl/rsa.h> #include <openssl/pem.h> ``` 2. 生成RSA密钥对 使用openssl库生成RSA密钥对,示例代码如下: ```c RSA *rsa = RSA_new(); BIGNUM *bne = BN_new(); unsigned long e = RSA_F4; if (BN_set_word(bne, e) != 1) { // error } if (RSA_generate_key_ex(rsa, 2048, bne, NULL) != 1) { // error } // 获取公钥和私钥 EVP_PKEY *private_key = EVP_PKEY_new(); EVP_PKEY *public_key = EVP_PKEY_new(); if (EVP_PKEY_assign_RSA(private_key, rsa) != 1) { // error } if (EVP_PKEY_assign_RSA(public_key, rsa) != 1) { // error } // 释放bne BN_free(bne); ``` 3. RSA加解密算法 使用openssl库进行RSA加解密算法,示例代码如下: ```c // 加密 unsigned char *encrypt_data = NULL; int encrypt_length = RSA_public_encrypt(data_length, data, encrypt_data, public_key, RSA_PKCS1_PADDING); // 解密 unsigned char *decrypt_data = NULL; int decrypt_length = RSA_private_decrypt(encrypt_length, encrypt_data, decrypt_data, private_key, RSA_PKCS1_PADDING); ``` 4. RSA签名验证算法 使用openssl库进行RSA签名验证算法,示例代码如下: ```c // 签名 unsigned char *sign_data = NULL; unsigned int sign_length = 0; EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); EVP_SignInit(md_ctx, EVP_sha256()); EVP_SignUpdate(md_ctx, data, data_length); EVP_SignFinal(md_ctx, sign_data, &sign_length, private_key); // 验证签名 EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); EVP_VerifyInit(md_ctx, EVP_sha256()); EVP_VerifyUpdate(md_ctx, data, data_length); int verify_result = EVP_VerifyFinal(md_ctx, sign_data, sign_length, public_key); ``` 以上是基本的思路,您可以参考这些代码来使用openssl库实现RSA加解密算法和RSA签名验证算法。当然,具体的实现细节和代码结构还需要根据您的具体需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值