Open xuyingjie opened 8 years ago
function arrayBufferToStr(buf) {
// return String.fromCharCode.apply(null, new Uint16Array(buf)) // >100kb string -> chrome 'Unhandled promise rejection RangeError: Maximum call stack size exceeded'
return (new Uint16Array(buf)).reduce((str, x) => str + String.fromCharCode(x), '')
}
function strToArrayBuffer(str) {
// return Uint16Array.from([...str], s => s.charCodeAt(0)).buffer
// return new Uint16Array([...str].map(s => s.charCodeAt(0))).buffer
return new TextEncoder('utf-16').encode(str)
}
//
// WebCryptoAPI
//
// concat arrayBuffer
function set(a, b) {
var out = new Uint8Array(a.byteLength + b.byteLength)
out.set(new Uint8Array(a))
out.set(new Uint8Array(b), a.byteLength)
return out.buffer
}
function importKey(passwd) {
return new Promise((resolve, reject) => {
window.crypto.subtle.importKey(
'raw', // can be 'jwk' or 'raw'
strToArrayBuffer(passwd),
{ // this is the algorithm options
name: 'AES-GCM',
},
false, // whether the key is extractable (i.e. can be used in exportKey)
['encrypt', 'decrypt'] // can 'encrypt', 'decrypt', 'wrapKey', or 'unwrapKey'
)
.then(key => {
// returns the symmetric key
resolve(key)
})
.catch(err => {
reject(err)
})
})
}
function encrypt(passwd, data) {
return new Promise((resolve, reject) => {
importKey(passwd)
.then(key => {
var iv = window.crypto.getRandomValues(new Uint8Array(12))
window.crypto.subtle.encrypt({
name: 'AES-GCM',
// Don't re-use initialization vectors! Always generate a new iv every time your encrypt!
// Recommended to use 12 bytes length
// where iv is an ArrayBuffer or an ArrayBufferView with 12 random bytes (these should be generated by RandomSource.getRandomValues()).
iv
},
key, // from generateKey or importKey above
data // ArrayBuffer of data you want to encrypt
)
.then(encrypted => {
// returns an ArrayBuffer containing the encrypted data
resolve(set(iv.buffer, encrypted))
})
.catch(err => {
reject(err)
})
})
})
}
function decrypt(passwd, data) {
return new Promise((resolve, reject) => {
importKey(passwd)
.then(key => {
window.crypto.subtle.decrypt({
name: 'AES-GCM',
iv: data.slice(0, 12) // The initialization vector you used to encrypt
},
key, // from generateKey or importKey above
data.slice(12) // ArrayBuffer of the data
)
.then(decrypted => {
// returns an ArrayBuffer containing the decrypted data
resolve(decrypted)
})
.catch(err => {
reject(err)
})
})
})
}
Ep: https://jswebcrypto.azurewebsites.net/demo.html#/ https://github.com/usrz/js-app-login/blob/master/index.html https://github.com/mozilla/releases-comm-central/blob/master/chat/modules/ArrayBufferUtils.jsm
function b64HmacSHA1(key, str) {
var keyBuf = new TextEncoder('utf-8').encode(key)
var buf = new TextEncoder('utf-8').encode(str)
return new Promise(resolve => {
var hmacSha1 = {name: 'hmac', hash: {name: 'sha-1'}}
crypto.subtle.importKey('raw', keyBuf, hmacSha1, true, ['sign', 'verify']).then(out => {
crypto.subtle.sign(hmacSha1, out, buf).then(result => {
resolve(btoa(String.fromCharCode.apply(null, new Uint8Array(result))))
})
})
})
}
Documents: http://javascript.ruanyifeng.com/stdlib/arraybuffer.html
arraybuffer.byteLength
arraybuffer.slice(begin[, end])
typedarray.buffer
typedarray.byteLength
typedarray.byteOffset
typedarray.length
typedarray.set(array [,offset])
typedarray.set(typedarray [,offset])
typedarray.subarray([begin [,end]])
// 是对于TypedArray数组的一部分,再建立一个新的视图。
typedarray.slice([begin[, end]])
// 返回一个指定位置的新的TypedArray实例。
TypedArray.of(element0[, element1[, ...[, elementN]]])
TypedArray.from(source[, mapFn[, thisArg]])
// Uint16Array.from(Uint8Array.of(0, 1, 2))
// [len][json][enc]
- export function decStr(data) {
- return new Promise(resolve => {
- var x = new Uint16Array(data)
- var len = x.subarray(0, 1)[0]
- var str = String.fromCharCode.apply(null, x.subarray(1, len + 1))
- var item = JSON.parse(str)
- if (localStorage.user) {
- let user = JSON.parse(localStorage.user)
- let buf = data.slice((len + 1) * 2)
- decrypt(user.passwd, buf).then(out => {
- item.text = arrayBufferToStr(out)
- resolve(item)
- })
- } else {
- item.text = ''
- resolve(item)
- }
- })
-}
new TextEncoder('utf-16').encode(str)
Note: Firefox, Chrome and Opera had support for encoding types other than utf-8 (such as: utf-16, iso-8859-2, koi8, cp1261, and gbk). As of Firebox 48 (ticket), Chrome 54 (ticket) and Opera 41, no other encoding types are available other than utf-8, in order to match the spec. In all cases, passing in an encoding type to the constructor will be ignored and a utf-8 TextEncoder will be created (Note: the TextDecoder still allows for other decoding types).
String.fromCharCode.apply(null, new Uint16Array(buf))
>100kb string -> chrome 'Unhandled promise rejection RangeError: Maximum call stack size exceeded'
http://www.w3.org/TR/WebCryptoAPI/
Example: https://github.com/diafygi/webcrypto-examples https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
Comparion: https://jsperf.com/webcrypto-sjcl-cryptojs/11
Chromium: Access to the WebCrypto API is restricted to secure origins (which is to say https:// pages)