前记
在前后端分离开发方式普遍流行的今天;有些信息很重要(如 密码、密钥、金额资产等);有些要求在传输过程严格防止第三方中间人攻击,所以要加密,有些信息要防止篡改,所以要传输方签名,接收方验签。虽然这些手段不一定能完全做到百分百安全,但也是系统安全堡垒不可缺少的一砖一瓦。下面我就介绍一下RSA算法的签名与验签
jsrsasign签名和验签
前端需要用js加密库 jsrsasign;
1.安装
npm install jsrsasign -S
2.封装签名、验签函数 rsa.js
import {KJUR, KEYUTIL, hex2b64, b64tohex} from 'jsrsasign'
const ALGORITHM = 'MD5withRSA'
/**
* 私钥签名
* rsa 用 MD5withRSA 算法签名
* @param privateKey 私钥
* @param src 明文
* @return {*}
* @constructor
*/
const RSA_SIGN = (privateKey, src) => {
const signature = new KJUR.crypto.Signature({'alg': ALGORITHM})
const priKey = KEYUTIL.getKey(privateKey) // 因为后端提供的是pck#8的密钥对,所以这里使用 KEYUTIL.getKey来解析密钥
signature.init(priKey) // 初始化实例
signature.updateString(src) // 传入待签明文
const a = signature.sign() // 签名, 得到16进制字符结果
return hex2b64(a) // 转换成base64
}
/**
* 公钥验签
* @param publicKey 公钥
* @param src 明文
* @param data 经过私钥签名并且转换成base64的结果
* @return {Boolean} 是否验签成功
* @constructor
*/
const RSA_VERIFY_SIGN = (publicKey, src, data) => {
const signature = new KJUR.crypto.Signature({'alg': ALGORITHM, 'prvkeypem': publicKey})
signature.updateString(src) // 传入待签明文
return signature.verify(b64tohex(data))
}
export {
RSA_SIGN,
RSA_VERIFY_SIGN
}
实验一下:
import {RSA_SIGN, RSA_VERIFY_SIGN} from './js/rsa'
const src = '123456'
const publicKey = '-----BEGIN PUBLIC KEY-----\n' +
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJRGV7eyd9peLPOIqFg3oionWq\n' +
'pmrjVik2wyJzWqv8it3yAvo/o4OR40ybrZPHq526k6ngvqHOCNJvhrN7wXNUEIT+\n' +
'PXyLuwfWP04I4EDBS3Bn3LcTMAnGVoIka0f5O6lo3I0YtPWwnyhcQhrHWuTietGC\n' +
'0CNwueI11Juq8NV2nwIDAQAB\n' +
'-----END PUBLIC KEY-----'
const privateKey = '-----BEGIN PRIVATE KEY-----\n' +
'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMlEZXt7J32l4s84\n' +
'ioWDeiKidaqmauNWKTbDInNaq/yK3fIC+j+jg5HjTJutk8ernbqTqeC+oc4I0m+G\n' +
's3vBc1QQhP49fIu7B9Y/TgjgQMFLcGfctxMwCcZWgiRrR/k7qWjcjRi09bCfKFxC\n' +
'Gsda5OJ60YLQI3C54jXUm6rw1XafAgMBAAECgYAii9PjcwsfPQcGTI0yR5QCN+J8\n' +
'jR4RsWtXk/zo0eptaaSY8rvjinx94Qb4Pb386s8jBE+HXRFG3SrJq9RI7LaPrGjU\n' +
'3qbURTExr9qRo9//eR9VahCKyftryRkeXGqBcOreDgbiTb6wYzUL9OdgSV4to4hz\n' +
'7oIBmnal3+oy5grpIQJBAOgQwoMgAQfjfDSeBcXRklLestWvHRxLu3mpgcvcqHWm\n' +
'eH6HdSxBidJlu0U14QkruvxOZAeW0Y4iu20LY0JKZY8CQQDeBneXmEJr1Pnd/GAU\n' +
'o61i9xpKJOmGmFaiM78DE+JYFdnim+wdye1z/u7GPuD6HmcQC3kb7zpSRVSdOWsn\n' +
'xvXxAkEAhJBWXMsia5wybmg6ifcebAJVDCW9LlXAoU4IHClPfe17dWPxtjc2AJ8m\n' +
'a/HMPA3kAY7SK1enG1eR00enCs4u1wJBAJitY9H4Xzyd0VGIul2XDKVwfUCdT4VB\n' +
'/tk9sk2gf9bI9/Mv+9ekQ0iv92yWUslM3NyYtyixgq6OhJg1ou1QkVECQB3Vu4Kv\n' +
'KafP5ejMPe3XplyDI20HJbHlAWH5NGZ67oRWLsVnKAIyLxZRhF4LPXew3gC9BVFC\n' +
'w8zj1geO42oOAso=\n' +
'-----END PRIVATE KEY-----'
console.log('明文:', src)
const data = RSA_SIGN(privateKey, src)
console.log('签名后的结果:', data)
console.log('--------------下面开始验签---------------')
const res = RSA_VERIFY_SIGN(publicKey, src, data)
console.log('验签结果:', res)
结果:
这里是演示了一下签名与验签的流程,前端借助 jsrsasign这个库;如果你后端是nodejs的话,可以用这个库来验签;后端是java或者其他后端语言也问题不大,都有一些相关的库;
接下来说一下我遇到的一些注意点:
1.怎么生成 公私钥,你可以百度一下 在线生成rsa 公私钥;
比如点击这个网址http://web.chacuo.net/netrsakeypair
生成密钥位数可选 1024位 / 2048位,因为目前最大破解位数好像是768位就不要选 512位的了,不够安全;密钥格式选择 pkcs#8 ; 证书密码 可填可不填,点击生成密钥对就行了,下面两个输入框就是新生成的公钥和私钥,然后复制好备份,私钥给前端,公钥给服务端同学;然后前端用私钥签名,后端用公钥验签。
明文:最好不要写死如'123456' 是一个唯一的字符串,每次签名的明文都不一样这样得到的签名结果就不一样。
2.签名算法:
const ALGORITHM = 'MD5withRSA'
上面写道 'MD5withRSA',当然不止这一种, 还有其他的 如 SHA1withRSA、
SHA1withDSA 、SHA1withECDSA等 可以参考文档https://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html
注意签名和验签时的算法要一致
3.签名结果如何在get请求中传输?
我的建议是:
当你以get请求的时候,参数会格式化为key=value;而签名结果里面刚好有+、/、=字符串,这时候就需要encodeURIComponent一下,后端接收时decodeURIComponent一下再验签;
4.公钥签名时记得加上开头 '-----BEGIN PUBLIC KEY-----' 与 结尾 '-----END PUBLIC KEY-----',这个不能少,在联调的过程中,可能会出现验签失败的情况,这时候前端和后端都可能觉得自己没有写错,怀疑是对方的错。这时候首先是前后端都要基于自己的封装好的签名和验签方法在同一个公钥签名,同一个私钥验签的情况下做对比,这样就能知道是谁签名不正确或者谁没有正确的验签了。
如何保证防篡改?
经过上面的例子,我们知道签名需要 两个参数 (明文、私钥)
验签需要三个参数(公钥、明文、签名结果)
在传输过程中, 假如明文被篡改了,那么服务端在接收后验签肯定不对,这时候就不必理会这次请求了,甚至可以做一下异常请求日志
总结
以上就是一些rsa的签名与验签使用经验,当然还有加密和解密的用法,那就不是本文章要说的了,如有疑惑或者不对请指出来,不胜感激,谢谢。