翼度科技»论坛 编程开发 JavaScript 查看内容

前端RSA密钥生成和加解密——window.crypto使用相关

8

主题

8

帖子

24

积分

新手上路

Rank: 1

积分
24
转自简书,原文地址,本文介绍window.crypto关于RSA方面的API。


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
  1. window.crypto.subtle.generateKey(
  2.     {
  3.         name: "RSA-OAEP",
  4.         modulusLength: 2048,
  5.         publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
  6.         hash: {
  7.             name: "SHA-512" // 这边如果后端使用公钥加密,要注意与前端一致
  8.         },
  9.     },
  10.     true,
  11.     ["encrypt", "decrypt"] // must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
  12. )
复制代码
函数结果返回一个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风格。
  1. // 导出私钥
  2. window.crypto.subtle.exportKey(
  3.     "pkcs8", // 公钥的话这边填spki
  4.     key.privateKey // 公钥这边是publicKey
  5. ).then(function(keydata2) {
  6.     let privateKey = RSA2text(keydata1, 1) // 私钥pem
  7. }).catch(function(err) {
  8.     console.error(err)
  9. })
  10. // pem格式文本
  11. function RSA2text(buffer, isPrivate = 0) {
  12.     let binary = ''
  13.     const bytes = new Uint8Array(buffer)
  14.     const len = bytes.byteLength
  15.     for (let i = 0; i < len; i++) {
  16.         binary += String.fromCharCode(bytes[i])
  17.     }
  18.     const base64 = window.btoa(binary)
  19.     let text = "-----BEGIN " + (isPrivate ? "PRIVATE" : "PUBLIC") + " KEY-----\n" // 这里-----BEGIN和-----是固定的
  20.     text += base64.replace(/[^\x00-\xff]/g, "$&\x01").replace(/.{64}\x01?/g, "$&\n") // 中间base64编码
  21.     text += "\n-----END " + (isPrivate ? "PRIVATE" : "PUBLIC") + " KEY-----" // 这里-----END和-----是固定的
  22.     return text
  23. }
复制代码
导入公私钥

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
  1. // 导入公钥
  2. const pub = "-----BEGIN PUBLIC KEY-----
  3. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo5NwYVVSg6rmAIKoxvCI
  4. 4Rn7FYh0mOFrnr0q2+r99/ZGuYCj5b6FQ8BwaaU8XpRn/y3W7W2bCggNRwllWQ2r
  5. dHIM+6vN2Yi/QYntKqbcRNlK1s02G2lw9pERaWi15+5P8+AFR8IHANm/Dd/19OlM
  6. 5FZ9hh+qG7FXFhV2i4r62pUZxhk6ykItOT16IH5poK9eEDhqsXZ+3UW6cGlxANgO
  7. jHJEnZpNCI5tS/4kFhLogHvEd88MoapljL6cZXk3ZafvxgUwxI6BZIhlw0adh2sj
  8. bByIHitjRxqKMDH7uSdV9zf8t5Wa0bZFcUpcb5Jx2QBWIlO1qP+Q4LLMbNvEHeBC
  9. 4wIDAQAB
  10. -----END PUBLIC KEY-----"
  11. const pemHeader = "-----BEGIN PUBLIC KEY-----" // 之前RSA2text函数里面的头尾标识,这个是公钥的
  12. const pemFooter = "-----END PUBLIC KEY-----"
  13. const pemContents = pub.substring(pemHeader.length, pub.length - pemFooter.length) // 去除pem头尾
  14. // base64解码
  15. const binaryDer = window.atob(pemContents)
  16. // 转为ArrayBuffer二进制字符串
  17. const binary = str2ab(binaryDer)
  18. window.crypto.subtle.importKey(
  19.     "spki", // 这边如果私钥则是pkcs8
  20.     binary ,
  21.     {
  22.         name: "RSA-OAEP",
  23.         hash: "SHA-512" // 保持一致
  24.     },
  25.     true,
  26.     ["encrypt"] // 用于加密所以是公钥,私钥就是decrypt
  27. )
  28. function str2ab(str) {
  29.     const buf = new ArrayBuffer(str.length)
  30.     const bufView = new Uint8Array(buf)
  31.     for (let i = 0, strLen = str.length; i < strLen; i++) {
  32.         bufView[i] = str.charCodeAt(i)
  33.     }
  34.     return buf
  35. }
复制代码
加密解密

加密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字符串
  1. const enc = new TextEncoder()
  2. const data = enc.encode("sucks") // 这边将要加密的字符串转为utf-8的Uint8Array
  3. window.crypto.subtle.encrypt(
  4.     {
  5.         name: "RSA-OAEP"
  6.     },
  7.     publicKey, // 生成或者导入的CryptoKey对象
  8.     data
  9. )
复制代码
解密crypto.subtle.decrypt(algorithm, key, data),基本同加密,这边data对应为加密返回的ArrayBuffer,如果是base64字符串比如从后端加密过来的,就需要转为Uint8Array。
  1. function base64ToUint8Array(base64String) {
  2.     const padding = '='.repeat((4 - base64String.length % 4) % 4)
  3.     const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
  4.     const rawData = window.atob(base64)
  5.     const outputArray = new Uint8Array(rawData.length)
  6.     for (let i = 0; i < rawData.length; ++i) {
  7.         outputArray[i] = rawData.charCodeAt(i)
  8.     }
  9.     return outputArray
  10. }
复制代码
返回值同加密

来源:https://www.cnblogs.com/charleschwang/p/18331401
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具