admin管理员组文章数量:1295726
I am evaluating how WebCrypto performance pares to third-party crypto libraries SJCL and Forge. I would expect WebCrypto to be much faster since it is a native browser implementation. This has also been benchmarked before and has shown such.
I have implemented the following tests using Benchmark.js to test key derivation (PBKDF2-SHA256), encrypt (AES-CBC), and decrypt (AES-CBC). These tests show web crypto to be significantly slower than both SJCL and Forge for encrypt/decrypt.
Benchmark Code
See fiddle here: /
var iterations = 5000;
var keySize = 256;
sjcl.beware['CBC mode is dangerous because it doesn\'t protect message integrity.']();
// =========================================================
// Preputed enc values for decrypt benchmarks
// =========================================================
var encIv = 'FX7Y3pYmcLIQt6WrKc62jA==';
var encCt = 'EDlxtzpEOfGIAIa8PkCQmA==';
// =========================================================
// Preputed keys for benchmarks
// =========================================================
function sjclMakeKey() {
return sjcl.misc.pbkdf2('mypassword', 'a salt', iterations, keySize, null);
}
var sjclKey = sjclMakeKey();
function forgeMakeKey() {
return forge.pbkdf2('mypassword', 'a salt', iterations, keySize / 8, 'sha256');
}
var forgeKey = forgeMakeKey();
var webcryptoKey = null;
window.crypto.subtle.importKey(
'raw', fromUtf8('mypassword'), {
name: 'PBKDF2'
},
false, ['deriveKey', 'deriveBits']
).then(function(importedKey) {
window.crypto.subtle.deriveKey({
'name': 'PBKDF2',
salt: fromUtf8('a salt'),
iterations: iterations,
hash: {
name: 'SHA-256'
}
},
importedKey, {
name: 'AES-CBC',
length: keySize
},
true, ['encrypt', 'decrypt']
).then(function(derivedKey) {
webcryptoKey = derivedKey;
});
});
// =========================================================
// IV helpers for encrypt benchmarks so all are using same PRNG methods
// =========================================================
function getRandomSjclBytes() {
var bytes = new Uint32Array(4);
return window.crypto.getRandomValues(bytes);
}
function getRandomForgeBytes() {
var bytes = new Uint8Array(16);
window.crypto.getRandomValues(bytes);
return String.fromCharCode.apply(null, bytes);
}
// =========================================================
// Serialization helpers for web crypto
// =========================================================
function fromUtf8(str) {
var strUtf8 = unescape(encodeURIComponent(str));
var ab = new Uint8Array(strUtf8.length);
for (var i = 0; i < strUtf8.length; i++) {
ab[i] = strUtf8.charCodeAt(i);
}
return ab;
}
function toUtf8(buf, inputType) {
inputType = inputType || 'ab';
var bytes = new Uint8Array(buf);
var encodedString = String.fromCharCode.apply(null, bytes),
decodedString = decodeURIComponent(escape(encodedString));
return decodedString;
}
function fromB64(str) {
var binary_string = window.atob(str);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
function toB64(buf) {
var binary = '';
var bytes = new Uint8Array(buf);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
// =========================================================
// The benchmarks
// =========================================================
$("#makekey").click(function() {
console.log('Starting test: Make Key (PBKDF2)');
var suite = new Benchmark.Suite;
suite
.add('SJCL', function() {
sjclMakeKey();
})
.add('Forge', function() {
forgeMakeKey();
})
.add('WebCrypto', {
defer: true,
fn(deferred) {
window.crypto.subtle.importKey(
'raw', fromUtf8('mypassword'), {
name: 'PBKDF2'
},
false, ['deriveKey', 'deriveBits']
).then(function(importedKey) {
window.crypto.subtle.deriveKey({
'name': 'PBKDF2',
salt: fromUtf8('a salt'),
iterations: iterations,
hash: {
name: 'SHA-256'
}
},
importedKey, {
name: 'AES-CBC',
length: keySize
},
true, ['encrypt', 'decrypt']
).then(function(derivedKey) {
window.crypto.subtle.exportKey('raw', derivedKey)
.then(function(exportedKey) {
deferred.resolve();
});
});
});
}
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('plete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({
'async': true
});
});
// =========================================================
// =========================================================
$("#encrypt").click(function() {
console.log('Starting test: Encrypt');
var suite = new Benchmark.Suite;
suite
.add('SJCL', function() {
var response = {};
var params = {
mode: 'cbc',
iv: getRandomSjclBytes()
};
var ctJson = sjcl.encrypt(sjclKey, 'some message', params, response);
var result = {
ct: ctJson.match(/"ct":"([^"]*)"/)[1],
iv: sjcl.codec.base64.fromBits(response.iv)
};
})
.add('Forge', function() {
var buffer = forge.util.createBuffer('some message', 'utf8');
var cipher = forge.cipher.createCipher('AES-CBC', forgeKey);
var ivBytes = getRandomForgeBytes();
cipher.start({
iv: ivBytes
});
cipher.update(buffer);
cipher.finish();
var encryptedBytes = cipher.output.getBytes();
var result = {
iv: forge.util.encode64(ivBytes),
ct: forge.util.encode64(encryptedBytes)
};
})
.add('WebCrypto', {
defer: true,
fn(deferred) {
var ivBytes = window.crypto.getRandomValues(new Uint8Array(16));
window.crypto.subtle.encrypt({
name: 'AES-CBC',
iv: ivBytes
}, webcryptoKey, fromUtf8('some message')).then(function(encrypted) {
var ivResult = toB64(ivBytes);
var ctResult = toB64(encrypted);
deferred.resolve();
});
}
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('plete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({
'async': true
});
});
// =========================================================
// =========================================================
$("#decrypt").click(function() {
console.log('Starting test: Decrypt');
var suite = new Benchmark.Suite;
suite
.add('SJCL', function() {
var ivBits = sjcl.codec.base64.toBits(encIv);
var ctBits = sjcl.codec.base64.toBits(encCt);
var aes = new sjcl.cipher.aes(sjclKey);
var messageBits = sjcl.mode.cbc.decrypt(aes, ctBits, ivBits, null);
var result = sjcl.codec.utf8String.fromBits(messageBits);
})
.add('Forge', function() {
var decIvBytes = forge.util.decode64(encIv);
var ctBytes = forge.util.decode64(encCt);
var ctBuffer = forge.util.createBuffer(ctBytes);
var decipher = forge.cipher.createDecipher('AES-CBC', forgeKey);
decipher.start({
iv: decIvBytes
});
decipher.update(ctBuffer);
decipher.finish();
var result = decipher.output.toString('utf8');
})
.add('WebCrypto', {
defer: true,
fn(deferred) {
var ivBytes = fromB64(encIv);
var ctBytes = fromB64(encCt);
window.crypto.subtle.decrypt({
name: 'AES-CBC',
iv: ivBytes
}, webcryptoKey, ctBytes).then(function(decrypted) {
var result = toUtf8(decrypted);
deferred.resolve();
});
}
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('plete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({
'async': true
});
});
Benchmark Results (Chrome)
Starting test: Make Key (PBKDF2)
SJCL x 26.31 ops/sec ±1.11% (37 runs sampled)
Forge x 13.55 ops/sec ±1.46% (26 runs sampled)
WebCrypto x 172 ops/sec ±2.71% (58 runs sampled)
Fastest is WebCrypto
Starting test: Encrypt
SJCL x 42,618 ops/sec ±1.43% (60 runs sampled)
Forge x 76,653 ops/sec ±1.76% (60 runs sampled)
WebCrypto x 18,011 ops/sec ±5.16% (47 runs sampled)
Fastest is Forge
Starting test: Decrypt
SJCL x 79,352 ops/sec ±2.51% (50 runs sampled)
Forge x 154,463 ops/sec ±2.12% (61 runs sampled)
WebCrypto x 22,368 ops/sec ±4.08% (53 runs sampled)
Fastest is Forge
Benchmark Results (Firefox)
Starting test: Make Key (PBKDF2)
SJCL x 20.21 ops/sec ±1.18% (34 runs sampled)
Forge x 11.63 ops/sec ±6.35% (30 runs sampled)
WebCrypto x 101 ops/sec ±9.68% (46 runs sampled)
Fastest is WebCrypto
Starting test: Encrypt
SJCL x 32,135 ops/sec ±4.37% (51 runs sampled)
Forge x 99,216 ops/sec ±7.50% (47 runs sampled)
WebCrypto x 11,458 ops/sec ±2.79% (52 runs sampled)
Fastest is Forge
Starting test: Decrypt
SJCL x 87,290 ops/sec ±4.35% (45 runs sampled)
Forge x 114,086 ops/sec ±6.76% (46 runs sampled)
WebCrypto x 10,170 ops/sec ±3.69% (42 runs sampled)
Fastest is Forge
What is going on here? Why is WebCrypto so much slower for encrypt/decrypt functions? Am I using Benchmark.js incorrectly or something?
I am evaluating how WebCrypto performance pares to third-party crypto libraries SJCL and Forge. I would expect WebCrypto to be much faster since it is a native browser implementation. This has also been benchmarked before and has shown such.
I have implemented the following tests using Benchmark.js to test key derivation (PBKDF2-SHA256), encrypt (AES-CBC), and decrypt (AES-CBC). These tests show web crypto to be significantly slower than both SJCL and Forge for encrypt/decrypt.
Benchmark Code
See fiddle here: https://jsfiddle/kspearrin/1Lzvpzkz/
var iterations = 5000;
var keySize = 256;
sjcl.beware['CBC mode is dangerous because it doesn\'t protect message integrity.']();
// =========================================================
// Preputed enc values for decrypt benchmarks
// =========================================================
var encIv = 'FX7Y3pYmcLIQt6WrKc62jA==';
var encCt = 'EDlxtzpEOfGIAIa8PkCQmA==';
// =========================================================
// Preputed keys for benchmarks
// =========================================================
function sjclMakeKey() {
return sjcl.misc.pbkdf2('mypassword', 'a salt', iterations, keySize, null);
}
var sjclKey = sjclMakeKey();
function forgeMakeKey() {
return forge.pbkdf2('mypassword', 'a salt', iterations, keySize / 8, 'sha256');
}
var forgeKey = forgeMakeKey();
var webcryptoKey = null;
window.crypto.subtle.importKey(
'raw', fromUtf8('mypassword'), {
name: 'PBKDF2'
},
false, ['deriveKey', 'deriveBits']
).then(function(importedKey) {
window.crypto.subtle.deriveKey({
'name': 'PBKDF2',
salt: fromUtf8('a salt'),
iterations: iterations,
hash: {
name: 'SHA-256'
}
},
importedKey, {
name: 'AES-CBC',
length: keySize
},
true, ['encrypt', 'decrypt']
).then(function(derivedKey) {
webcryptoKey = derivedKey;
});
});
// =========================================================
// IV helpers for encrypt benchmarks so all are using same PRNG methods
// =========================================================
function getRandomSjclBytes() {
var bytes = new Uint32Array(4);
return window.crypto.getRandomValues(bytes);
}
function getRandomForgeBytes() {
var bytes = new Uint8Array(16);
window.crypto.getRandomValues(bytes);
return String.fromCharCode.apply(null, bytes);
}
// =========================================================
// Serialization helpers for web crypto
// =========================================================
function fromUtf8(str) {
var strUtf8 = unescape(encodeURIComponent(str));
var ab = new Uint8Array(strUtf8.length);
for (var i = 0; i < strUtf8.length; i++) {
ab[i] = strUtf8.charCodeAt(i);
}
return ab;
}
function toUtf8(buf, inputType) {
inputType = inputType || 'ab';
var bytes = new Uint8Array(buf);
var encodedString = String.fromCharCode.apply(null, bytes),
decodedString = decodeURIComponent(escape(encodedString));
return decodedString;
}
function fromB64(str) {
var binary_string = window.atob(str);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
function toB64(buf) {
var binary = '';
var bytes = new Uint8Array(buf);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
// =========================================================
// The benchmarks
// =========================================================
$("#makekey").click(function() {
console.log('Starting test: Make Key (PBKDF2)');
var suite = new Benchmark.Suite;
suite
.add('SJCL', function() {
sjclMakeKey();
})
.add('Forge', function() {
forgeMakeKey();
})
.add('WebCrypto', {
defer: true,
fn(deferred) {
window.crypto.subtle.importKey(
'raw', fromUtf8('mypassword'), {
name: 'PBKDF2'
},
false, ['deriveKey', 'deriveBits']
).then(function(importedKey) {
window.crypto.subtle.deriveKey({
'name': 'PBKDF2',
salt: fromUtf8('a salt'),
iterations: iterations,
hash: {
name: 'SHA-256'
}
},
importedKey, {
name: 'AES-CBC',
length: keySize
},
true, ['encrypt', 'decrypt']
).then(function(derivedKey) {
window.crypto.subtle.exportKey('raw', derivedKey)
.then(function(exportedKey) {
deferred.resolve();
});
});
});
}
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('plete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({
'async': true
});
});
// =========================================================
// =========================================================
$("#encrypt").click(function() {
console.log('Starting test: Encrypt');
var suite = new Benchmark.Suite;
suite
.add('SJCL', function() {
var response = {};
var params = {
mode: 'cbc',
iv: getRandomSjclBytes()
};
var ctJson = sjcl.encrypt(sjclKey, 'some message', params, response);
var result = {
ct: ctJson.match(/"ct":"([^"]*)"/)[1],
iv: sjcl.codec.base64.fromBits(response.iv)
};
})
.add('Forge', function() {
var buffer = forge.util.createBuffer('some message', 'utf8');
var cipher = forge.cipher.createCipher('AES-CBC', forgeKey);
var ivBytes = getRandomForgeBytes();
cipher.start({
iv: ivBytes
});
cipher.update(buffer);
cipher.finish();
var encryptedBytes = cipher.output.getBytes();
var result = {
iv: forge.util.encode64(ivBytes),
ct: forge.util.encode64(encryptedBytes)
};
})
.add('WebCrypto', {
defer: true,
fn(deferred) {
var ivBytes = window.crypto.getRandomValues(new Uint8Array(16));
window.crypto.subtle.encrypt({
name: 'AES-CBC',
iv: ivBytes
}, webcryptoKey, fromUtf8('some message')).then(function(encrypted) {
var ivResult = toB64(ivBytes);
var ctResult = toB64(encrypted);
deferred.resolve();
});
}
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('plete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({
'async': true
});
});
// =========================================================
// =========================================================
$("#decrypt").click(function() {
console.log('Starting test: Decrypt');
var suite = new Benchmark.Suite;
suite
.add('SJCL', function() {
var ivBits = sjcl.codec.base64.toBits(encIv);
var ctBits = sjcl.codec.base64.toBits(encCt);
var aes = new sjcl.cipher.aes(sjclKey);
var messageBits = sjcl.mode.cbc.decrypt(aes, ctBits, ivBits, null);
var result = sjcl.codec.utf8String.fromBits(messageBits);
})
.add('Forge', function() {
var decIvBytes = forge.util.decode64(encIv);
var ctBytes = forge.util.decode64(encCt);
var ctBuffer = forge.util.createBuffer(ctBytes);
var decipher = forge.cipher.createDecipher('AES-CBC', forgeKey);
decipher.start({
iv: decIvBytes
});
decipher.update(ctBuffer);
decipher.finish();
var result = decipher.output.toString('utf8');
})
.add('WebCrypto', {
defer: true,
fn(deferred) {
var ivBytes = fromB64(encIv);
var ctBytes = fromB64(encCt);
window.crypto.subtle.decrypt({
name: 'AES-CBC',
iv: ivBytes
}, webcryptoKey, ctBytes).then(function(decrypted) {
var result = toUtf8(decrypted);
deferred.resolve();
});
}
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('plete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({
'async': true
});
});
Benchmark Results (Chrome)
Starting test: Make Key (PBKDF2)
SJCL x 26.31 ops/sec ±1.11% (37 runs sampled)
Forge x 13.55 ops/sec ±1.46% (26 runs sampled)
WebCrypto x 172 ops/sec ±2.71% (58 runs sampled)
Fastest is WebCrypto
Starting test: Encrypt
SJCL x 42,618 ops/sec ±1.43% (60 runs sampled)
Forge x 76,653 ops/sec ±1.76% (60 runs sampled)
WebCrypto x 18,011 ops/sec ±5.16% (47 runs sampled)
Fastest is Forge
Starting test: Decrypt
SJCL x 79,352 ops/sec ±2.51% (50 runs sampled)
Forge x 154,463 ops/sec ±2.12% (61 runs sampled)
WebCrypto x 22,368 ops/sec ±4.08% (53 runs sampled)
Fastest is Forge
Benchmark Results (Firefox)
Starting test: Make Key (PBKDF2)
SJCL x 20.21 ops/sec ±1.18% (34 runs sampled)
Forge x 11.63 ops/sec ±6.35% (30 runs sampled)
WebCrypto x 101 ops/sec ±9.68% (46 runs sampled)
Fastest is WebCrypto
Starting test: Encrypt
SJCL x 32,135 ops/sec ±4.37% (51 runs sampled)
Forge x 99,216 ops/sec ±7.50% (47 runs sampled)
WebCrypto x 11,458 ops/sec ±2.79% (52 runs sampled)
Fastest is Forge
Starting test: Decrypt
SJCL x 87,290 ops/sec ±4.35% (45 runs sampled)
Forge x 114,086 ops/sec ±6.76% (46 runs sampled)
WebCrypto x 10,170 ops/sec ±3.69% (42 runs sampled)
Fastest is Forge
What is going on here? Why is WebCrypto so much slower for encrypt/decrypt functions? Am I using Benchmark.js incorrectly or something?
Share Improve this question edited Mar 14, 2017 at 1:00 kspearrin asked Mar 13, 2017 at 20:11 kspearrinkspearrin 10.8k10 gold badges57 silver badges92 bronze badges 3- 2 1. PBKDF2 is not siupposed to be fast, a good reasonably secure value is 100ms. 2. if "some message" is the data being encrypted you are mainly measuring AES setup time. – zaph Commented Mar 13, 2017 at 20:29
- 4 @zaph I realize that PBKDF2 is not suppose to be fast, however, when benchmarking it against other PBKDF2 implementations you can hope for better performance. This will allow me to introduce more iterations without sacrificing wait time from the client. The benchmarks show PBKDF2 as being much faster with web crypto anyways. The question is mainly geared towards the encrypt/decrypt tests. – kspearrin Commented Mar 13, 2017 at 20:56
- 1 @zaph the PBKDF2 algorithm is not supposed to be fast, but a PBKDF2 implementation is supposed to be as fast (efficient) as is possible. An adversary looking to crack passwords will be using a highly optimized implementation in their attack. If you are using a slow implementation you are either making their job easier or your life harder. – Chris_F Commented Mar 10, 2021 at 2:22
1 Answer
Reset to default 11I have a hunch that, with such a short message length, you're mostly measuring invocation overhead. With its asynchronous promise-based interface, WebCrypto probably loses out a bit there.
I modified your encryption benchmark to use a 1.5 kib plaintext, and the results look very different:
Starting test: Encrypt
SJCL x 3,632 ops/sec ±2.20% (61 runs sampled)
Forge x 2,968 ops/sec ±3.02% (60 runs sampled)
WebCrypto x 5,522 ops/sec ±6.94% (42 runs sampled)
Fastest is WebCrypto
With a 96 kib plaintext, the difference is even greater:
Starting test: Encrypt
SJCL x 56.77 ops/sec ±5.43% (49 runs sampled)
Forge x 48.17 ops/sec ±1.12% (41 runs sampled)
WebCrypto x 162 ops/sec ±4.53% (45 runs sampled)
Fastest is WebCrypto
本文标签: javascriptBenchmarking WebCrypto is much slower than thirdparty librariesStack Overflow
版权声明:本文标题:javascript - Benchmarking WebCrypto is much slower than third-party libraries? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741620805a2388798.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论