700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 前后端跨语言RSA加解密和签名验证实现(js+python)

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

时间:2023-03-29 06:13:03

相关推荐

前后端跨语言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="/blueimp-md5/2.10.0/js/md5.min.js"></script>//加密库(本次用RSA算法加密)<script src="/ajax/libs/jsencrypt/3.2.1/jsencrypt.min.js"></script>//签名库<script src="/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_5import Crypto.Signature.PKCS1_v1_5 as sign_PKCS1_v1_5 # 用于签名/验签from Crypto.PublicKey import RSAimport base64from Crypto.Hash import MD5

密钥生成

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

生成的密钥格式如下,将其保存为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_md5console.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 / falsemsg11 = msg1.split("|", 1)[0] //mmsg12 = 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 + "|" + message2console.log("原文|签名:",temp);console.log("服务端公钥加密↓");message = this.encrypt_server_public.encrypt(temp);console.log("公钥加密:",message);

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。