前端RSA密钥生成和加解密window.crypto

crypto API支持常用的rsa、aes加解密,这边介绍rsa的应用。

浏览器兼容性

window.crypto需要chrome 37版本,ie 11,safari 11才支持全部API而基本的加解密在safari 7就可以。

生成公私钥

crypto.subtle.generateKey(algorithm, extractable, keyUsages),其中:
1.algorithm参数根据不同算法填入对应的参数对,rsa需要填入RsaHashedKeyGenParams对象包含有:

  • name,可选RSASSA-PKCS1-v1_5, RSA-PSS, or RSA-OAEP,这边如果用于加解密是不支持旧的RSAES-PKCS1-v1_5的(jsencrypt.js支持),RSASSA-PKCS1-v1_5用于签名

  • modulusLength,为rsa位数,推荐至少2048位(相当于112位的aes)才能较为安全,此参数最为影响性能,比如1024比2048快非常多,NIST建议目前的RSA秘钥安全强度是2048位,如果需要工作到2030年之后,就使用3072位的秘钥

  • publicExponent,一般直接为[0x01, 0x00, 0x01]

  • hash,摘要方式,可选SHA-256SHA-384SHA-512,这边也允许SHA-1,但是因为其安全性所以基本不建议

2.extractable一般是true,表示是否允许以文本的方式导出key

3.keyUsages是一个数组,里面可选encryptdecryptsign

window.crypto.subtle.generateKey(
    {
        name: "RSA-OAEP",
        modulusLength: 2048,
        publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
        hash: {
            name: "SHA-512" // 这边如果后端使用公钥加密,要注意与前端一致
        },
    },
    true,
    ["encrypt", "decrypt"] // must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
)

函数结果返回一个promise对象,如果是对称加密会得到一个密钥CryptoKey类型,这边rsa会得到一个密钥对CryptoKeyPair,它有2个CryptoKey成员,privateKeypublicKey,我们导出密钥为文本或者加解密都将通过这2个成员对象。

导出公私钥

window.crypto.subtle.exportKey(format, key),其中:
1.format可选rawpkcs8spkijwk,我们这边在导出公钥时选spki,私钥选pkcs8

2.key就是上面CryptoKeyPairprivateKey或者publicKey
函数返回一个promise对象,结果是一个ArrayBuffer,这边转成pem风格。

// 导出私钥
 window.crypto.subtle.exportKey(
    "pkcs8", // 公钥的话这边填spki
    key.privateKey // 公钥这边是publicKey
).then(function(keydata2) {
    let privateKey = RSA2text(keydata1, 1) // 私钥pem
}).catch(function(err) {
    console.error(err)
})
// pem格式文本
function RSA2text(buffer, isPrivate = 0) {
    let binary = ''
    const bytes = new Uint8Array(buffer)
    const len = bytes.byteLength
    for (let i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i])
    }
    const base64 = window.btoa(binary)
    let text = "-----BEGIN " + (isPrivate ? "PRIVATE" : "PUBLIC") + " KEY-----\n" // 这里-----BEGIN和-----是固定的
    text += base64.replace(/[^\x00-\xff]/g, "$&\x01").replace(/.{64}\x01?/g, "$&\n") // 中间base64编码
    text += "\n-----END " + (isPrivate ? "PRIVATE" : "PUBLIC") + " KEY-----" // 这里-----END和-----是固定的
    return text
}

导入公私钥

window.crypto.subtle.importKey(
format,
keyData,
algorithm,
extractable,
keyUsages
)
,其中:
1.format可选rawpkcs8spkijwk,对应之前生成时的选择,我们这边在导入公钥时选spki,私钥选pkcs8

2.keyData,即window.crypto.subtle.exportKey获得的ArrayBuffer,由于在这里时我们一般只有pem文本的,所以还需要做转换成ArrayBuffer。

3.algorithm这边我们是rsa,需要填入一个RsaHashedImportParams对象,这边对应crypto.subtle.generateKey所需的RsaHashedKeyGenParams对象,含有:

  • name,都保持与之前一致
  • hash

4.extractablecrypto.subtle.generateKey

5.keyUsagescrypto.subtle.generateKey
函数返回一个promise对象,结果是一个CryptoKey

// 导入公钥
const pub = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo5NwYVVSg6rmAIKoxvCI
4Rn7FYh0mOFrnr0q2+r99/ZGuYCj5b6FQ8BwaaU8XpRn/y3W7W2bCggNRwllWQ2r
dHIM+6vN2Yi/QYntKqbcRNlK1s02G2lw9pERaWi15+5P8+AFR8IHANm/Dd/19OlM
5FZ9hh+qG7FXFhV2i4r62pUZxhk6ykItOT16IH5poK9eEDhqsXZ+3UW6cGlxANgO
jHJEnZpNCI5tS/4kFhLogHvEd88MoapljL6cZXk3ZafvxgUwxI6BZIhlw0adh2sj
bByIHitjRxqKMDH7uSdV9zf8t5Wa0bZFcUpcb5Jx2QBWIlO1qP+Q4LLMbNvEHeBC
4wIDAQAB
-----END PUBLIC KEY-----"

const pemHeader = "-----BEGIN PUBLIC KEY-----" // 之前RSA2text函数里面的头尾标识,这个是公钥的
const pemFooter = "-----END PUBLIC KEY-----"
const pemContents = pub.substring(pemHeader.length, pub.length - pemFooter.length) // 去除pem头尾
// base64解码
const binaryDer = window.atob(pemContents)
// 转为ArrayBuffer二进制字符串
const binary = str2ab(binaryDer)
window.crypto.subtle.importKey(
    "spki", // 这边如果私钥则是pkcs8
    binary , 
    {
        name: "RSA-OAEP",
        hash: "SHA-512" // 保持一致
    },
    true, 
    ["encrypt"] // 用于加密所以是公钥,私钥就是decrypt
)

function str2ab(str) {
    const buf = new ArrayBuffer(str.length)
    const bufView = new Uint8Array(buf)
    for (let i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i)
    }
    return buf
}

加密解密

加密crypto.subtle.encrypt(algorithm, key, data),其中:
1.algorithm,加解密只支持RSA-OAEP不支持RSAES-PKCS1-v1_5

2.key即公钥的CryptoKey对象

3.data是一个BufferSource对象,不能直接是要加密的字符串。
结果是一个ArrayBuffer,可以使用window.btoa(String.fromCharCode(...new Uint8Array(e)))输出为base64字符串

const enc = new TextEncoder()
const data = enc.encode("sucks") // 这边将要加密的字符串转为utf-8的Uint8Array
window.crypto.subtle.encrypt(
    {
        name: "RSA-OAEP"
    },
    publicKey, // 生成或者导入的CryptoKey对象
    data
)

解密crypto.subtle.decrypt(algorithm, key, data),基本同加密,这边data对应为加密返回的ArrayBuffer,如果是base64字符串比如从后端加密过来的,就需要转为Uint8Array。

function base64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4)
    const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')

    const rawData = window.atob(base64)
    const outputArray = new Uint8Array(rawData.length)

    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i)
    }
    return outputArray
}

返回值同加密

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容