学习笔记:使用jsrsasign 进行加密、解密、签名和验签操作
最编程
2024-08-14 08:07:49
...
你将会收获:
- js如何加密, 解密
- js如何签名, 验签
- js和Java交互如何相互解密, 验签(重点)
通过谷歌, 发现jsrsasign
库使用者较多. 查看api发现这个库功能很健全. 本文使用方法, 是结合网上千篇一律的博文, 加上我自己查看源码总结出来的.
- 公用代码:
// 公钥
let pk="-----BEGIN PUBLIC KEY-----\n" +
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD3XSdz1MnzazBEN5KOfTx0IyVJ\n" +
"Z5wb57isrCuHDhnYXwtmdhQalgII0fozeeFpMpAvlnmHC1kpW7XVGvZnLx3bWbCE\n" +
"bf+pMSW4kmQuI+5cxRUJbCl7sdaODBrINgERHPICVC18AJLThEVMHyjuR6Jn4zQm\n" +
"yYNbReSktY/BrFTvMQIDAQAB\n" +
"-----END PUBLIC KEY-----";
// 私钥
let priK = "-----BEGIN PRIVATE KEY-----\n" +
"MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAPddJ3PUyfNrMEQ3\n" +
"ko59PHQjJUlnnBvnuKysK4cOGdhfC2Z2FBqWAgjR+jN54WkykC+WeYcLWSlbtdUa\n" +
"9mcvHdtZsIRt/6kxJbiSZC4j7lzFFQlsKXux1o4MGsg2AREc8gJULXwAktOERUwf\n" +
"KO5HomfjNCbJg1tF5KS1j8GsVO8xAgMBAAECgYEA6eG1JMrj63jEmStmMb1txG1a\n" +
"mu4Q5z2QGgtr2HVXsIIlGEq6tWxyHf7TL4qkuz9onuYKn8n2Eqm44fZtVaBx+5ES\n" +
"zRpIvlTvaxmVu0HZ1hYAzUw1XyRnXNMKpL5tT4GCjm8+QGPzlGxgXI1sNg8r9Jaw\n" +
"9zRUYeA6LQR9RIMkHWUCQQD8QojjVoGjtiunoh/N8iplhUszZIavAEvmDIE+kVy+\n" +
"pA7hvlukLw6JMc7cfTcnHyxDo9iHVIzrWlTuKRq9KWVLAkEA+wgJS2sgtldnCVn6\n" +
"tJKFVwsHrWhMIU29msPPbNuWUD23BcKE/vehIyFu1ahNA/TiM40PEnzprQ5JfPxU\n" +
"16S78wJANTfMLTnYy7Lo7sqTLx2BuD0wqjzw9QZ4/KVytsJv8IAn65P/PVn4FRV+\n" +
"8KEx+3zmF7b/PT2nJRe/hycAzxtmlQJBAMrFwQxEqpXfoAEzx4lY2ZBn/nmaR/SW\n" +
"4VNEXCbocVC7qT1j1R5HVMgV13uKiTtq8dUGWmhqsi7x3XayNK5ECPUCQQDZaAN6\n" +
"tvIHApz9OLsXSw0jZirQ6KEYdharXbIVDy1W1sVE3lzLbqLdFp1bxAHQIvsYS5PM\n" +
"A9veSJh372RLJKkj\n" +
"-----END PRIVATE KEY-----";
// 原文
var src = "好厉害";
jsrsasign加密和解密
加密
- 传入pem标准格式的秘钥字符串, 解析生成秘钥实例:
RSAKey
. 标准的pem格式秘钥含有开始标记
和结束标记
, 如本文使用的秘钥:-----BEGIN xxx-----
,-----END xxx-----
. 至于xxx的具体内容不是太重要, 代码里自动通过正则清洗掉头和尾标记, 所以真的写成-----BEGIN xxx-----
也没有关系. - 调用
encrypt
方法, 传入明文和公钥实例, 加密后的返回值是16进制字符串. - 所以, 需要将其转为常用的Base64编码. 如果为了方便放在URL上, 建议使用使用
hextob64u(enc)
, 它会将+
替换成-
,/
替换成_
,去掉尾部补全的=
. 不建议使用encodeURIComponent
, 这种编码方式会更大程度上扩大原数据的体积(Base64只会增加1/3, 而url采用的16进制方式, 会增加1倍, 具体原因可另外谷歌).
解密
基本类似加密流程.
// 加密
// 读取解析pem格式的秘钥, 生成秘钥实例 (RSAKey)
var pub = KEYUTIL.getKey(pk);
var enc = KJUR.crypto.Cipher.encrypt(src,pub);
// console.log(enc);
// console.log(hextob64(enc));
// 解密
var prv = KEYUTIL.getKey(priK);
var dec = KJUR.crypto.Cipher.decrypt(enc,prv);
console.log("jsrsasign decrypt: "+dec);
jsrsasign签名和验签
通用流程
RSA签名验签基本流程如下, 当然, 都会被封装成两个方法搞定: 签名和验签.
签名:
- 指定一款摘要算法, 如sha1对原文哈希.
- 上述哈希前面填补上摘要算法标识, 便于验签时识别用的什么算法.
- 用rsa私钥对上述哈希加密.
- 完成签名.
验签:
- 用rsa公钥对签名解密, 得到摘要.
- 原文取摘要.
- 对比两个摘要, 一样则验签通过, 否则验签不通过.
使用jsrsasign签名验签
签名
网上资料很多比较雷同, 在签名时代码开起来比较麻烦.
这里先给出大家通常步骤, 最后给出我自己看源码总结简化调用方式.
方式1: 创建秘钥实例 -> 构建Signature实例 -> 传入秘钥实例, 初始化 -> 签名
// 方式1: 先建立 key 对象, 构建 signature 实例, 传入 key 初始化 -> 签名
var key = KEYUTIL.getKey(priK);
console.log(key);
// 创建 Signature 对象
let signature=new KJUR.crypto.Signature({alg:"SHA1withRSA"});
// 传入key实例, 初始化signature实例
signature.init(key);
// 传入待签明文
signature.updateString(src);
// 签名, 得到16进制字符结果
let a = signature.sign();
let sign = hextob64(a);
console.log(sign);
方式2: 我的简化方式: 方式1的基础上, 去掉显示读取私钥, 去掉初始化步骤(init(..)
)
// 创建 Signature 对象
let signature=new KJUR.crypto.Signature({alg:"SHA1withRSA",prvkeypem:priK}); //!这里指定 私钥 pem!
signature.updateString(src);
let a = signature.sign();
let sign = hextob64(a);
console.log(sign);
验签
注意点看注释.
// 验签
// !要重新new 一个Signature, 否则, 取摘要和签名时取得摘要不一样, 导致验签误报失败(原因不明)!
let signatureVf = new KJUR.crypto.Signature({alg:"SHA1withRSA",prvkeypem:pk});
signatureVf.updateString(src);
// !接受的参数是16进制字符串!
let b = signatureVf.verify(b64tohex(sign));
console.log("jsrsasign verify: "+b);
jsrsasign和Java交互
这是很关键的, 任何js插件在好用, 如果和Java不能兼容, 也是白搭. 之前就是过jsencrypt.js
库, 但是发现Java在签名验签时貌似不兼容.
// 解密Java的密文
var prv = KEYUTIL.getKey(priK);
// Java加密的密文(Base64Url)
let encJava = "8S2KlcygY8eUvq_Dzro81IQd6oA5fxW9l9hsy8iOvtByMMJI1wKedO5sR_pJmJFYEZl6wfD4BQ-FzvSYftnO5xO8kJaHNtnrFE7R0mqpLIkf6aN02K4F9zWLad3emFTN8Ze_GqooVaa0oX6XHqpDFBQJF3kUB6cfS9mDJNq_boE";
// 解密 / Base64Url -> 16进制 / 私钥实例
var dec4Java = KJUR.crypto.Cipher.decrypt(b64utohex(encJava), prv);
console.log("jsrsasign decrypt 4 java: "+dec4Java);
// 验证Java的签名
// 构建Signature实例
// 这里 prvkeypem 放公钥pem看起来有点怪, 但是这是可行的, 内部还是使用的上文经常出现的 KEYUTIL.getKey(pk) 来生成公钥实例的
var sign4Java = new KJUR.crypto.Signature({alg:"SHA1withRSA",prvkeypem:pk});
sign4Java.updateString(src);
// Java生成签名
var signByJava = "O6uEQFPPEmRfEiZcLQjMB7yYLpO2ohmCJvn95Izu8LveUWqFtoYJbvWRYwKCCV-Z3iurjpEw5nExvHQghwoYIxpB7p97G29WXWhfiaA0AUNlxDM2cOus-CIAq-Kyqee7vDsewp6ixaHThu0CxoPFGpBTpo5kuOFlPFR6CRS3Q9M";
var b2 = sign4Java.verify(b64utohex(signByJava));
console.log("jsrsasign verify 4 java: " + b2);
本文测试代码的运行结果:
jsrsasign signing: O6uEQFPPEmRfEiZcLQjMB7yYLpO2ohmCJvn95Izu8LveUWqFtoYJbvWRYwKCCV+Z3iurjpEw5nExvHQghwoYIxpB7p97G29WXWhfiaA0AUNlxDM2cOus+CIAq+Kyqee7vDsewp6ixaHThu0CxoPFGpBTpo5kuOFlPFR6CRS3Q9M=
jsrsasign verify: true
jsrsasign decrypt: 好厉害
jsrsasign decrypt 4 java: 好厉害
jsrsasign verify 4 java: true
附录: jsrsasign部分方法源码
本来想讲测试用的源文件附上来, 但是这里貌似不支持附件, 所以部分主要的方法代码. 通过阅读, 加上了部分注释, 所以api看起来更容易理解. 另外, 本文调用方式是在页面引入js方式使用的, 若使用其他框架, 可能调用方式略有区别, 但是核心api是不变的.
/**
*
* @param l RSAKey / ECDSA / DSA / 标准的pem格式秘钥Base64字符
* @param k
* @param n
* @returns {*}
*/
KEYUTIL.getKey = function (l, k, n) {
var G = ASN1HEX, L = G.getChildIdx, v = G.getV, d = G.getVbyList, c = KJUR.crypto, i = c.ECDSA, C = c.DSA,
w = RSAKey, M = pemtohex, F = KEYUTIL;
...
// 这里通过判断pem结束标记来判断传入的是什么类型的秘钥字符
if (l.indexOf("-END PUBLIC KEY-") != -1) {
var O = pemtohex(l, "PUBLIC KEY");
return F._getKeyFromPublicPKCS8Hex(O)
}
if (l.indexOf("-END RSA PRIVATE KEY-") != -1 && l.indexOf("4,ENCRYPTED") == -1) {
var m = M(l, "RSA PRIVATE KEY");
return F.getKey(m, null, "pkcs5prv")
}
...
/**
*
* @param {String} e 明文
* @param {RSAKey} f 公钥
* @param {String} d 算法名称, 大写, 如 RSA, 缺省 RSA
* @returns {String} 16进制字符串
*/
KJUR.crypto.Cipher.encrypt = function (e, f, d) {
if (f instanceof RSAKey && f.isPublic) {
var c = KJUR.crypto.Cipher.getAlgByKeyAndName(f, d);
if (c === "RSA") {
return f.encrypt(e)
}
if (c === "RSAOAEP") {
return f.encryptOAEP(e, "sha1")
}
var b = c.match(/^RSAOAEP(\d+)$/);
if (b !== null) {
return f.encryptOAEP(e, "sha" + b[1])
}
throw"Cipher.encrypt: unsupported algorithm for RSAKey: " + d
} else {
throw"Cipher.encrypt: unsupported key or algorithm"
}
};
/**
*
* @param {String} e 16进制密文字符串
* @param {RSAKey} f 私钥
* @param {String} d 算法名称, 大写, 如 RSA, 缺省 RSA
* @returns {String} 明文
*/
KJUR.crypto.Cipher.decrypt = function (e, f, d) {
if (f instanceof RSAKey && f.isPrivate) {
var c = KJUR.crypto.Cipher.getAlgByKeyAndName(f, d);
if (c === "RSA") {
return f.decrypt(e)
}
if (c === "RSAOAEP") {
return f.decryptOAEP(e, "sha1")
}
var b = c.match(/^RSAOAEP(\d+)$/);
if (b !== null) {
return f.decryptOAEP(e, "sha" + b[1])
}
throw"Cipher.decrypt: unsupported algorithm for RSAKey: " + d
} else {
throw"Cipher.decrypt: unsupported key or algorithm"
}
};
/**
*
* @param {Object}o o.alg:算法名称; o.prov:支持的js文件标识; o.prvkeypem:pem格式秘钥(base64);
* @constructor
*/
KJUR.crypto.Signature = function (o) {
var q = null;
...
/**签名方法*/
this.sign = function () {
...
} else {
if (this.prvKey instanceof RSAKey && this.pubkeyAlgName === "rsa") {
this.hSign = this.prvKey.signWithMessageHash(this.sHashHex, this.mdAlgName)
...
推荐阅读
-
学习笔记:使用jsrsasign 进行加密、解密、签名和验签操作
-
iCloud 切换区域,中国区保留 appStore(更新)--自 2018 年 2 月 28 日起,中国区 iCloud 由云上贵州管理 苹果公司发布的公告 https://support.apple.com/zh-cn/HT208352 关键词 关键部分 受影响的 iCloud 账户:国家或地区设置为 "中国 "的 Apple ID。 iCloud 包含的服务照片、邮件、通讯录、日历、提醒事项、备忘、书签、钱包、钥匙串、云备份、云驱动器、应用程序数据 新条款和条件: 同意仅出于本协议允许的目的并在中国法律允许的范围内使用服务。 云桂洲在提供服务时应使用合理的技能并尽职尽责,但在适用法律允许的最大范围内,我们不保证或担保您通过本服务存储或访问的任何内容不会意外损坏、崩溃、丢失或根据本协议的条款被删除,如果发生此类损坏、崩溃、丢失或删除,我们不承担任何责任。您应自行负责维护您的信息和数据的适当备份。 Apple 和云上贵州有权访问您存储在服务中的所有数据,包括有权根据适用法律相互之间共享、交换和披露所有用户数据(包括内容)。 本协议的解释、效力和履行应适用*法律。对于因本协议引起的或与本协议有关的任何争议,云桂洲和您同意提交中国国际经济贸易仲裁委员会(CIETAC)根据提交仲裁时有效的法律在北京进行具有约束力的仲裁。 由云桂洲管理,用户选择: 停用; ID 到地区; 受 iCloud(由云桂洲运营)条款和条件约束 首先,我想说说我对数据安全的看法。 当我在朋友圈发布通知时,有些朋友回复说国外的操作并没有多安全,或者国外的安全只是相对于国外而言的等等。首先,我非常感谢这些朋友,这让我反思什么是数据安全。以下观点均属个人观点: 国外的月亮一定比国内圆? 这是一个根深蒂固的问题,只要有人说国外的东西比国内好,就会有人嘲笑崇洋媚外。我觉得我们在某些方面应该向国外学习,比如搜索引擎和版权问题。打开百度搜索 "数据安全",第一行肯定是广告。打开谷歌搜索 "数据安全",第一条就是 "数据安全_百度百科" .....各种版权问题大家都明白,支持正版,但不仅客户一心想找免费破解,就连作者也往往没有保护自己劳动成果或产品的想法。但从另一个层面来说,国内的发展和安全,甩国外几条街。没有说哪里好,哪里不好,辩证地去学习更好。 国外也有别有用心的数据泄露,谈何安全? 从加密解密的角度看,自古以来就没有绝对安全的加密,只有相对安全的做法。苹果的棱镜门、微软的 cpu 漏洞,各种参差不齐的被破解案例 ....是的,这的确是一个很好的论据,但凡事都不能只看一面,当年苹果面对FBI破解手机的要求,几经论证,苹果还是拒绝破解。这点拿到国内,只要上面的文件传达下去,还有企业敢说不吗?还敢说不吗? 关于这次iCloud数据迁移个人看法? 把数据迁移到贵州的云端,相当于把手机的所有数据都存储在贵州的云端服务器上。也许访问数据的速度会快很多,但我会把我的iCloud区放到美国,因为我不想数据存在云上贵州后经常接到莫名其妙的电话或短信,更不想因为乱用国外服务器而被请去喝茶。iCloud一个ID,即从中国账号转到美国区,主要用于数据存在美国服务器上。appStore一个ID,除了注册一个中国ID外,专门用来下载应用用,因为国外ID不支持酷狗和网易云等应用。麻烦的是,用了新的 appStore ID 后,当前的应用还得重新下载安装,因为旧的应用 ID 与新的应用 ID 不兼容,安装不了。最后,iCloud迁移后,国内用户使用美国服务器,估计要 "扶墙 "了。 专业步骤: 首先,进行appleID设置,这是前提条件,否则无法选择转移区域! 取消 appleID 的双重认证 取消家庭共享选项 二、窗口下载并安装 icloud 3.0 版
-
使用RSA加密证书进行JWT签名和验签的IdentityServer4