在我的Web应用程序中,当用户注销我的应用程序时,我试图将数据存储在本地存储中,并在再次登录后将其还原。此数据是私有数据,因此在保存之前需要进行加密。由于这一要求,该过程如下所示:
加密:
解密:
这是代码(TypeScript):
interface Data {
queue: string;
initializationVector: string;
date: string;
}
private getEncryptionKey(): void {
const date: string = this.getDateParamForEncryptionKeyGeneration();
const params = new HttpParams().set('date', date);
this.encryptionKeyDate = DateSerializer.deserialize(date);
this.http.get(this.ENCRYPTION_KEY_ENDPOINT, {params}).subscribe((response: {key: string}) => {
const seed = response.key.slice(0, 32);
window.crypto.subtle.importKey(
'raw',
new TextEncoder().encode(seed),
'AES-GCM',
true,
['encrypt', 'decrypt']
).then(
(key: CryptoKey) => {
this.encryptionKey = key;
this.decrypt();
}
);
});
}
private getDateParamForEncryptionKeyGeneration(): string {
const dataAsString: string = this.localStorageService.getItem(...);
const data: Data = dataAsString ? JSON.parse(dataAsString) : null;
return data ? data.date : DateSerializer.serialize(moment());
}
private decrypt(data: Data): void {
const encoder = new TextEncoder();
const encryptionAlgorithm: AesGcmParams = {
name: 'AES-GCM',
iv: encoder.encode(data.initializationVector)
};
window.crypto.subtle.decrypt(
encryptionAlgorithm,
this.encryptionKey,
encoder.encode(data.queue)
).then(
(decryptedData: ArrayBuffer) => {
const decoder = new TextDecoder();
console.log(JSON.parse(decoder.decode(decryptedData)));
}
);
}
private encrypt(queue: any[]): void {
const initializationVector: Uint8Array = window.crypto.getRandomValues(new Uint8Array(12));
const encryptionAlgorithm: AesGcmParams = {
name: 'AES-GCM',
iv: initializationVector
};
window.crypto.subtle.encrypt(
encryptionAlgorithm,
this.encryptionKey,
new TextEncoder().encode(JSON.stringify(queue))
).then((encryptedQueue: ArrayBuffer) => {
const decoder = new TextDecoder();
const newState: Data = {
queue: decoder.decode(encryptedQueue),
initializationVector: decoder.decode(initializationVector),
date: DateSerializer.serialize(this.encryptionKeyDate)
};
this.localStorageService.setItem('...', JSON.stringify(newState));
});
}
第一个问题是解密后我收到了DOMException
。这几乎是不可能调试的,因为由于安全问题,浏览器会隐藏实际错误:
error: DOMException
code: 0
message: ""
name: "OperationError"
另一件事是我在质疑我的方法-生成这样的加密密钥是否正确?我怀疑这可能是问题的根源,但是我找不到任何使用Web Crypto API从字符串生成加密密钥的方法。
此外,作为加密密钥来源的字符串的长度为128个字符,到目前为止,我只是采用前32个字符来获取256位数据。我不确定这是否正确,因为开头的字符可能不是唯一的。散列可能是一个很好的答案吗?
任何帮助/指导将不胜感激,尤其是验证我的方法。我正在努力寻找类似问题的任何例子。谢谢!
答案 0 :(得分:1)
我也不是安全专家。这么说...
一种方法是在客户端上生成密钥,而无需从后端服务器请求唯一的字符串。使用该密钥进行加密,将密钥保存到后端服务器,然后再次获取密钥以进行解密。
这是用JavaScript编写的,并且在TypeScript中也能正常工作。
const runDemo = async () => {
const messageOriginalDOMString = 'Do the messages match?';
//
// Encode the original data
//
const encoder = new TextEncoder();
const messageUTF8 = encoder.encode(messageOriginalDOMString);
//
// Configure the encryption algorithm to use
//
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const algorithm = {
iv,
name: 'AES-GCM',
};
//
// Generate/fetch the cryptographic key
//
const key = await window.crypto.subtle.generateKey({
name: 'AES-GCM',
length: 256
},
true, [
'encrypt',
'decrypt'
]
);
//
// Run the encryption algorithm with the key and data.
//
const messageEncryptedUTF8 = await window.crypto.subtle.encrypt(
algorithm,
key,
messageUTF8,
);
//
// Export Key
//
const exportedKey = await window.crypto.subtle.exportKey(
'raw',
key,
);
// This is where to save the exported key to the back-end server,
// and then to fetch the exported key from the back-end server.
//
// Import Key
//
const importedKey = await window.crypto.subtle.importKey(
'raw',
exportedKey,
"AES-GCM",
true, [
"encrypt",
"decrypt"
]
);
//
// Run the decryption algorithm with the key and cyphertext.
//
const messageDecryptedUTF8 = await window.crypto.subtle.decrypt(
algorithm,
importedKey,
messageEncryptedUTF8,
);
//
// Decode the decryped data.
//
const decoder = new TextDecoder();
const messageDecryptedDOMString = decoder.decode(messageDecryptedUTF8);
//
// Assert
//
console.log(messageOriginalDOMString);
console.log(messageDecryptedDOMString);
};
runDemo();
另一方面,如果需求需要加密密钥从后端的唯一,低熵字符串中得出,则deriveKey
方法可能适用于PBKDF2 algorithm。 / p>
const runDemo = async() => {
const messageOriginalDOMString = 'Do the messages match?';
//
// Encode the original data
//
const encoder = new TextEncoder();
const messageUTF8 = encoder.encode(messageOriginalDOMString);
//
// Configure the encryption algorithm to use
//
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const algorithm = {
iv,
name: 'AES-GCM',
};
//
// Generate/fetch the cryptographic key
//
function getKeyMaterial() {
let input = 'the-username' + new Date();
let enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(input), {
name: "PBKDF2"
},
false, ["deriveBits", "deriveKey"]
);
}
let keyMaterial = await getKeyMaterial();
let salt = window.crypto.getRandomValues(new Uint8Array(16));
let key = await window.crypto.subtle.deriveKey({
"name": "PBKDF2",
salt: salt,
"iterations": 100000,
"hash": "SHA-256"
},
keyMaterial, {
"name": "AES-GCM",
"length": 256
},
true, ["encrypt", "decrypt"]
);
//
// Run the encryption algorithm with the key and data.
//
const messageEncryptedUTF8 = await window.crypto.subtle.encrypt(
algorithm,
key,
messageUTF8,
);
//
// Export Key
//
const exportedKey = await window.crypto.subtle.exportKey(
'raw',
key,
);
// This is where to save the exported key to the back-end server,
// and then to fetch the exported key from the back-end server.
//
// Import Key
//
const importedKey = await window.crypto.subtle.importKey(
'raw',
exportedKey,
"AES-GCM",
true, [
"encrypt",
"decrypt"
]
);
//
// Run the decryption algorithm with the key and cyphertext.
//
const messageDecryptedUTF8 = await window.crypto.subtle.decrypt(
algorithm,
importedKey,
messageEncryptedUTF8,
);
//
// Decode the decryped data.
//
const decoder = new TextDecoder();
const messageDecryptedDOMString = decoder.decode(messageDecryptedUTF8);
//
// Assert
//
console.log(messageOriginalDOMString);
console.log(messageDecryptedDOMString);
};
runDemo();