Web Crypto API简介

早年在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的支持。

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相关的类

密钥操作

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

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

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);
});

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

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解码。

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加上实际密钥内容导入。

导出操作则相对简单

window.crypto.subtle.exportKey(
    "jwk", //密钥格式
    key //密钥
)
.then(function(keydata){
    console.log(keydata);
})
.catch(function(err){
    console.error(err);
});

加解密

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的格式

签名验签

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中)。

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

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java、Spring、技术分享

java SSL

防止抵赖,能够检查签名之后内容是否有更改。通过单向散列算法对内容进行求值,相当于对内容进行提取了指纹。

24920
来自专栏分布式系统和大数据处理

.Net中的加密解密

在一些比较重要的应用场景中,通过网络传递数据需要进行加密以保证安全。本文将简单地介绍了加密解密的一些概念,以及相关的数字签名、证书,最后介绍了如何在.NET中对...

15640
来自专栏程序员宝库

看图学 HTTPS

之前说到HTTPS,在我的概念中就是更安全,需要服务器配置证书,但是到底什么是HTTPS,为什么会更安全,整套流程又是如何实现的,在脑子里没有具体的概念。所以,...

18760
来自专栏osc同步分享-java技术分享站

MD5 加密和 BASE64 编码

package com.yawn.security; import java.security.MessageDigest; import java.util...

385110
来自专栏JetpropelledSnake

SNMP学习笔记之SNMPv3报文认证和加密

安全参数存在于snmp消息中的msgSecurityParameters字段,以ASN.1语法定义如下:

27830
来自专栏技术换美食换不换

TOB服务部署安全模块

15940
来自专栏程序员Gank

【译】在正确的线程上观察

尽管很多人了解RxJava的基本逻辑,但是在Observable链和操作符究竟运行在哪个线程,仍然会有许多困惑。

11820
来自专栏程序员同行者

Python3模块: hashlib

31720
来自专栏前端进阶之路

看图学HTTPS前言正文总结

之前说到HTTPS,在我的概念中就是更安全,需要服务器配置证书,但是到底什么是HTTPS,为什么会更安全,整套流程又是如何实现的,在脑子里没有具体的概念。所以,...

12840
来自专栏Golang语言社区

Go和HTTPS -2

F为签名函数。CA自己的私钥是唯一标识CA签名的,因此CA用于生成数字证书的签名函数一定要以自己的私钥作为一个输入参数。在RSA加密 系统中,发送端的解密函数就...

44770

扫码关注云+社区

领取腾讯云代金券