前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Web Crypto API简介

Web Crypto API简介

原创
作者头像
王沛文
发布2018-07-31 15:43:34
5.7K0
发布2018-07-31 15:43:34
举报
文章被收录于专栏:王沛文的专栏王沛文的专栏

早年在web端做对称/非对称的加解密还是个很复杂的操作,由于没有js层面的基础库。很多基础设施只能从头开始。

QQ登录注册之前使用的RSA加密算法就是参考http://www-cs-students.stanford.edu/~tjw/jsbn/的实现。

还有各种aes/md5/sha等常用算法的js库也是层出不穷。但是由于大多都是个人项目,很多库并没有很好的维护,对于不同的算法支持也不是很完整。比如基于https://github.com/travist/jsencrypt就缺少RSA/OEAP的支持,https://github.com/ricmoo/aes-js也缺少AES/GCM的支持。

当然近些年来Web标准突飞猛进。对于常用密码学套件来说,最大的新增特性就是Web Crypto API了。

Web Crypto API提供了常用算法的加密/解密/签名/验证/摘要/key生成/协商等操作,功能上和nodejs中的crypto模块基本等同,也就是Web端的OpenSSL了。

但是由于接口和nodejs中的crypto不同,Web Crypto API统一采用的Promise来处理异步逻辑,而不是nodejs中的回调。这样可以很方便的使用await/async简化代码。

摘要算法

针对摘要算法提供的是disgest接口,这个接口可以提供SHA-1/SHA-256/SHA-384/SHA-512的摘要算法。

对于MD5等老旧的算法是不支持的。SHA-1这里也很特殊标准之前是规定支持这个算法,但是由于SHA-1本身存在缺陷,已经建议不使用,从浏览器来看就是移除SHA-1的支持。

代码语言:txt
复制
window.crypto.subtle.digest(
    {
        name: "SHA-256",
    },
    new Uint8Array([1,2,3,4])
)
.then(function(hash){
    console.log(new Uint8Array(hash));
})
.catch(function(err){
    console.error(err);
});

这里比较特殊的就是接口的输入和输出都是ArrayBuffer相关的类

密钥操作

除了摘要算法之外,加解密签名都需要密钥来操作。涉及到的主要就是密钥生成/导入/导出。、

下面是生成密钥的示例代码

代码语言:txt
复制
window.crypto.subtle.generateKey(
    {
        name: "RSASSA-PKCS1-v1_5",//算法名称
        hash: {name: "SHA-256"}, //签名时的hash算法
        //RSA参数
        modulusLength: 2048, //私钥长度
        publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
        //EC参数
        namedCurve: "P-256",//曲线名字
        //AES参数
        length: 256, //密钥长度
    },
    false, //是否可以被导出
    ["sign", "verify"] //该key支持的操作
)
.then(function(key){
    //这里获取的key可以用于后续加解密签名等操作
    console.log(key);
})
.catch(function(err){
    console.error(err);
});

当然除了本地生成密钥还有导入外部密钥

代码语言:txt
复制
window.crypto.subtle.importKey(
    "jwk", //密钥格式
    {   //密钥内容
        kty: "oct",
        k: "Y0zt37HgOx-BY7SQjYVmrqhPkO44Ii2Jcb9yydUDPfE",
        alg: "A256CTR",
        ext: true,
    },
    {   //密钥算法
        name: "AES-CTR",
    },
    false, //是否能被导出
    ["encrypt", "decrypt"] //支持的操作模式
)

这里比较让人疑惑的就是密钥格式和密钥内容两个参数了

密钥格式和密钥内容

通常我们使用的密钥格式为PEM/DER。而上述例子中的jwk指的是JSON Web Key。具体可以参见rfc7517

对于常见的PEM格式我们需要使用其中有效内容部分。即BEGIN/END之间的部分所以我们可以将其中内容提取出来之后base64解码。

代码语言:txt
复制
function convertPemToBinary(pem) {
    var lines = pem.split('\n')
    var encoded = ''
    for (var i = 0; i < lines.length; i++) {
        if (lines[i].trim().length > 0 &&
            !/-----BEGIN .*-----/.test(lines[i]) &&
            !/-----END .*-----/.test(lines[i])) {
            encoded += lines[i].trim()
        }
    }
    return base64StringToArrayBuffer(encoded)
}
function base64StringToArrayBuffer(b64str) {
    var byteStr = atob(b64str)
    var bytes = new Uint8Array(byteStr.length)
    for (var i = 0; i < byteStr.length; i++) {
        bytes[i] = byteStr.charCodeAt(i)
    }
    return bytes.buffer
}

DER格式则就是PEM中具体的内容。

对于EC/RSA公钥使用pkcs8的PEM/DER格式的实际数据配合密钥格式spki就可以导入了。

而私钥则是pkcs8格式的实际数据配合密钥格式pkcs8

对称密钥则可以通过raw加上实际密钥内容导入。

导出操作则相对简单

代码语言:txt
复制
window.crypto.subtle.exportKey(
    "jwk", //密钥格式
    key //密钥
)
.then(function(keydata){
    console.log(keydata);
})
.catch(function(err){
    console.error(err);
});

加解密

代码语言:txt
复制
window.crypto.subtle.encrypt(
    {
        name: "RSA-OAEP",
    },
    publicKey,
    data
)
.then(function(encrypted){
    console.log(new Uint8Array(encrypted));
})
.catch(function(err){
    console.error(err);
});

window.crypto.subtle.decrypt(
    {
        name: "RSA-OAEP",
    },
    privateKey,
    data
)
.then(function(decrypted){
    console.log(new Uint8Array(decrypted));
})
.catch(function(err){
    console.error(err);
});

加解密的接口都类似,输入算法/密钥/数据,输出结果。这里输入输出的数据也都是ArrayBuffer的格式

签名验签

代码语言:txt
复制
window.crypto.subtle.sign(
    {
        name: "RSASSA-PKCS1-v1_5",
    },
    privateKey,
    data
)
.then(function(signature){
    console.log(new Uint8Array(signature));
})
.catch(function(err){
    console.error(err);
});

window.crypto.subtle.verify(
    {
        name: "RSASSA-PKCS1-v1_5",
    },
    publicKey,
    signature,
    data
)
.then(function(isvalid){
    console.log(isvalid);
})
.catch(function(err){
    console.error(err);
});

签名和验证签名的接口也是类似。

总结

Web Crypto API的入口是window.crypto.subtle

所有的接口都是window.crypto.subtle的方法。所有接口的返回都是Promise对象。

涉及密钥操作的算法需要先生成或导入密钥。导入密钥的格式有raw,spki,pkcs8,jwk。raw用于对称密钥直接导入的情况,spki则是DER格式的公钥,pkcs8时DER格式的pkcs8私钥,jwk则支持所有的场景,但是需要转换。

所有算法输入输出均为ArrayBuffer

补充

具体浏览器支持可以参见https://caniuse.com/#feat=cryptography

针对旧浏览器的polyfill/shim可以看https://github.com/vibornoff/webcrypto-shimhttps://github.com/PeculiarVentures/webcrypto-liner

常见的样例代码可以参见https://github.com/diafygi/webcrypto-examples

最后Web Crypto API由于属于安全接口,在非https的页面上可能不可用(chrome中)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 摘要算法
  • 密钥操作
    • 密钥格式和密钥内容
    • 加解密
    • 签名验签
    • 总结
      • 补充
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档