Contenu connexe Similaire à Dodging WebCrypto API Landmines (20) Dodging WebCrypto API Landmines3. Finalized! January 2017
WebCrypto API
- W3C
JavaScript API for performing basic cryptographic
operations in web applications, such as hashing,
signature generation and verification, and encryption
and decryption.
@erniewturner
7. Subtle Crypto
window.crypto.subtle.*
@erniewturner
It is named SubtleCrypto to reflect the fact that many of these algorithms
have subtle usage requirements in order to provide the required
algorithmic security guarantees. -W3C
It is named SubtleCrypto to reflect the fact that many of these algorithms
have subtle usage requirements in order to provide the required
algorithmic security guarantees. -W3C
8. Subtle Crypto
Developers making use of the SubtleCrypto interface are expected to be aware of
the security concerns associated with both the design and implementation of the
various algorithms provided. The raw algorithms are provided in order to allow
developers maximum flexibility in implementing a variety of protocols and
applications, each of which may represent the composition and security parameters
in a unique manner that necessitate the use of the raw algorithms.
-MDN
@erniewturner
9. Subtle Crypto
Methods are generic and take crypto algorithms as strings or objects
Nearly all operations return Promises
Only available over HTTPS
@erniewturner
19. const empty = new Uint8Array(32);
Uint8Array
[0, 0, 0, ...]
const fixed = new Uint8Array([35, 183, 21, 111]); [35, 183, 21, 111]
const text = UTF8.encode('text'); [116, 101, 120, 116]
const flag = UTF8.encode('!'); [240, 159, 135, 179, 240, 159, 135, 180]
@erniewturner
29. User Adds Data
User Enters Password
Convert Password to CryptoKey
Derive AES Key
Encrypt Data
Encrypt
@erniewturner
31. Dat Passcod Derive AES EncryImport
function encryptData(secretData: string, password: string){
const dataAsBytes = UTF8.encode(secretData);
const passwordAsBytes = UTF8.encode(password);
}
33. function encryptData(secretData: string, passwo
const dataAsBytes = UTF8.encode(secretData);
const passwordAsBytes = UTF8.encode(password)
}
Data to Encrypt
byte array form
User Passcode
in binary formbyte array form
36. function encryptData(secretData: string, password: string){
const dataAsBytes = UT8.encode(secretData);
const passwordAsBytes = UTF8.encode(password);
window.crypto.subtle.importKey(
“raw",
passwordAsBytes,
'PBKDF2',
false
[‘deriveKey’]
)
.then((passwordKey: CryptoKey) => {
});
}
37. function encryptData(secretData: string, password: string){
const dataAsBytes = UT8.encode(secretData);
const passwordAsBytes = UTF8.encode(password);
window.crypto.subtle.importKey(
“raw",
passwordAsBytes,
'PBKDF2',
false
[‘deriveKey’]
)
.then((passwordKey: CryptoKey) => {
});
}
38. function encryptData(secretData: string, password: string){
const dataAsBytes = UT8.encode(secretData);
const passwordAsBytes = UTF8.encode(password);
window.crypto.subtle.importKey(
“raw",
passwordAsBytes,
'PBKDF2',
false
[‘deriveKey’]
)
.then((passwordKey: CryptoKey) => {
});
}
39. function encryptData(secretData: string, passwor
const dataAsBytes = UT8.encode(secretData);
const passwordAsBytes = UTF8.encode(password);
window.crypto.subtle.importKey(
“raw",
passwordAsBytes,
'PBKDF2',
false
[‘deriveKey’]
)
.then((passwordKey: CryptoKey) => {
});
}
Data to Encrypt
byte array form
User Password
Crypto Key
42. .then((passwordKey: CryptoKey) => {
const salt = window.crypto.getRandomValues(new Uint8Array(12));
return window.crypto.subtle.deriveKey({
name: 'PBKDF2',
salt,
iterations: 250000,
hash: {name: 'SHA-256'}
}, passwordKey, {name: 'AES-GCM', length: 256}, false, ['encrypt']);
})
.then((aesKey: CryptoKey) => {
});
43. .then((passwordKey: CryptoKey) => {
const salt = window.crypto.getRandomValues(new Uint8Array(32));
return window.crypto.subtle.deriveKey({
name: 'PBKDF2',
salt,
iterations: 250000,
hash: {name: 'SHA-256'}
}, passwordKey, {name: 'AES-GCM', length: 256}, false, ['encrypt']);
})
.then((aesKey: CryptoKey) => {
});
44. .then((passwordKey: CryptoKey) => {
const salt = window.crypto.getRandomValues(new Uint8Array(32));
return window.crypto.subtle.deriveKey({
name: 'PBKDF2',
salt,
iterations: 250000,
hash: {name: 'SHA-256'}
}, passwordKey, {name: 'AES-GCM', length: 256}, false, ['encrypt']);
})
.then((aesKey: CryptoKey) => {
});
45. .then((passwordKey: CryptoKey) => {
const salt = window.crypto.getRandomValues(new Uint8Array(32));
return window.crypto.subtle.deriveKey({
name: 'PBKDF2',
salt,
iterations: 250000,
hash: {name: 'SHA-256'}
}, passwordKey, {name: 'AES-GCM', length: 256}, false, ['encrypt']);
})
.then((aesKey: CryptoKey) => {
});
46. .then((passwordKey: CryptoKey) => {
const salt = window.crypto.getRandomValues(new Uint8Array(32));
return window.crypto.subtle.deriveKey({
name: 'PBKDF2',
salt,
iterations: 250000,
hash: {name: 'SHA-256'}
}, passwordKey, {name: 'AES-GCM', length: 256}, false, ['encrypt']);
})
.then((aesKey: CryptoKey) => {
});
47. Data to Encrypt
byte array form
.then((passwordKey: CryptoKey) => {
const salt = window.crypto.getRandomValues(new U
return window.crypto.subtle.deriveKey({
name: 'PBKDF2',
salt,
iterations: 250000,
hash: {name: 'SHA-256'}
}, passwordKey, {name: 'AES-GCM', length: 256},
})
.then((aesKey: CryptoKey) => {
});
AES-GCM
Crypto Key
50. .then((aesKey: CryptoKey) => {
const iv = window.crypto.getRandomValues(new Uint8Array(12));
return window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv,
}, aesKey, dataAsBytes);
})
.then((encryptedContent: ArrayBuffer) => {
const encryptedBytes = new Uint8Array(encryptedContent);
});
51. .then((aesKey: CryptoKey) => {
const iv = window.crypto.getRandomValues(new Uint8Array(12));
return window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv,
}, aesKey, dataAsBytes);
})
.then((encryptedContent: ArrayBuffer) => {
const encryptedBytes = new Uint8Array(encryptedContent);
});
52. .then((aesKey: CryptoKey) => {
const iv = window.crypto.getRandomValues(new Uint8Array(12));
return window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv,
}, aesKey, dataAsBytes);
})
.then((encryptedContent: ArrayBuffer) => {
const encryptedBytes = new Uint8Array(encryptedContent);
});
53. .then((aesKey: CryptoKey) => {
const iv = window.crypto.getRandomValues(new Uint8Array(12));
return window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv,
}, aesKey, dataAsBytes);
})
.then((encryptedContent: ArrayBuffer) => {
const encryptedBytes = new Uint8Array(encryptedContent);
});
54. Encrypted Data
.then((aesKey: CryptoKey) => {
const iv = window.crypto.getRandomValues(new Uint8Ar
return window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv,
}, aesKey, dataAsBytes);
})
.then((encryptedContent: ArrayBuffer) => {
const encryptedBytes = new Uint8Array(encryptedConte
});
byte array form
55. Storage
256 bit = 32 byte (Uint8Array)
96 bit = 12 byte
(Uint8Array) > 0 bytes (Uint8Array)
@erniewturner
Salt Encrypted
Data
Initialization
Vector
PBKDF2 AES-GCM
59. .then((encryptedContent: ArrayBuffer) => {
const encryptedBytes = new Uint8Array(encryptedContent);
const encryptedPackage = concat(
salt,
iv,
encryptedBytes
);
return Base64.fromByteArray(encryptedPackage);
});
60. .then((encryptedContent: ArrayBuffer) => {
const encryptedBytes = new Uint8Array(encryptedContent);
const encryptedPackage = concat(
salt,
iv,
encryptedBytes
);
return Base64.fromByteArray(encryptedPackage);
});
61. .then((encryptedContent: ArrayBuffer) => {
const encryptedBytes = new Uint8Array(encryptedContent);
const encryptedPackage = concat(
salt,
iv,
encryptedBytes
);
return Base64.fromByteArray(encryptedPackage);
});
63. Get Encrypted Data
User Enters Password
Convert Password to CryptoKey
Derive AES Key
Decrypt Data
Decrypt
@erniewturner
66. function decryptData(encryptedData: string, password: string){
const encryptedBytes = Base64.toByteArray(encryptedData);
const salt = encryptedBytes.slice(0, 32);
const IV = encryptedBytes.slice(32, 12);
const encryptedData = encryptedBytes.slice(32 + 12);
}
67. function decryptData(encryptedData: string, password: string){
const encryptedBytes = Base64.toByteArray(encryptedData);
const salt = encryptedBytes.slice(0, 32);
const IV = encryptedBytes.slice(32, 12);
const encryptedData = encryptedBytes.slice(32 + 12);
}
68. function decryptData(encryptedData: string, password: string){
const encryptedBytes = Base64.toByteArray(encryptedData);
const salt = encryptedBytes.slice(0, 32);
const IV = encryptedBytes.slice(32, 12);
const encryptedData = encryptedBytes.slice(32 + 12);
}
70. return window.crypto.subtle.importKey(...)
.then(() => window.crypto.subtle.deriveKey(...))
.then((aesKey: CryptoKey) => {
return window.crypto.subtle.decrypt({
name: 'AES-GCM',
iv,
}, aesKey, encryptedData);
})
.then((decryptedContent: ArrayBuffer) => {
const decryptedBytes = new Uint8Array(decryptedContent);
});
}
71. return window.crypto.subtle.importKey(...)
.then(() => window.crypto.subtle.deriveKey(...))
.then((aesKey: CryptoKey) => {
return window.crypto.subtle.decrypt({
name: 'AES-GCM',
iv,
}, aesKey, encryptedData);
})
.then((decryptedContent: ArrayBuffer) => {
const decryptedBytes = new Uint8Array(decryptedContent);
});
}
72. return window.crypto.subtle.importKey(...)
.then(() => window.crypto.subtle.deriveKey(...))
.then((aesKey: CryptoKey) => {
return window.crypto.subtle.decrypt({
name: 'AES-GCM',
iv,
}, aesKey, encryptedData);
})
.then((decryptedContent: ArrayBuffer) => {
const decryptedBytes = new Uint8Array(decryptedContent);
});
}
73. return window.crypto.subtle.importKey(...)
.then(() => window.crypto.subtle.deriveKey(...))
.then((aesKey: CryptoKey) => {
return window.crypto.subtle.decrypt({
name: ‘AES-GCM',
iv,
}, aesKey, encryptedData);
})
.then((decryptedContent: ArrayBuffer) => {
const decryptedBytes = new Uint8Array(decryptedContent);
});
}
74. Important Notes
Key derivation will not fail if user enters wrong password
PBKDF2 Iterations must be the same on encrypt as on decrypt
There is no “forgot password” support
@erniewturner
There is no way to feature detect which algorithms are supported
82. WEB WORKERS
Bytes to encrypt/decrypt
User password bytes
Encrypted/decrypted bytes
MAIN
THREAD
WEB
WORKER