add support for decrypting AES-ECB mode (#6476)
This commit is contained in:
parent
7a32837bc7
commit
9212751553
|
@ -52,8 +52,16 @@ export abstract class CryptoFunctionService {
|
||||||
mac: string,
|
mac: string,
|
||||||
key: SymmetricCryptoKey
|
key: SymmetricCryptoKey
|
||||||
) => DecryptParameters<Uint8Array | string>;
|
) => DecryptParameters<Uint8Array | string>;
|
||||||
aesDecryptFast: (parameters: DecryptParameters<Uint8Array | string>) => Promise<string>;
|
aesDecryptFast: (
|
||||||
aesDecrypt: (data: Uint8Array, iv: Uint8Array, key: Uint8Array) => Promise<Uint8Array>;
|
parameters: DecryptParameters<Uint8Array | string>,
|
||||||
|
mode: "cbc" | "ecb"
|
||||||
|
) => Promise<string>;
|
||||||
|
aesDecrypt: (
|
||||||
|
data: Uint8Array,
|
||||||
|
iv: Uint8Array,
|
||||||
|
key: Uint8Array,
|
||||||
|
mode: "cbc" | "ecb"
|
||||||
|
) => Promise<Uint8Array>;
|
||||||
rsaEncrypt: (
|
rsaEncrypt: (
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
publicKey: Uint8Array,
|
publicKey: Uint8Array,
|
||||||
|
|
|
@ -99,7 +99,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.cryptoFunctionService.aesDecryptFast(fastParams);
|
return await this.cryptoFunctionService.aesDecryptFast(fastParams, "cbc");
|
||||||
}
|
}
|
||||||
|
|
||||||
async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise<Uint8Array> {
|
async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise<Uint8Array> {
|
||||||
|
@ -140,7 +140,8 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||||
const result = await this.cryptoFunctionService.aesDecrypt(
|
const result = await this.cryptoFunctionService.aesDecrypt(
|
||||||
encThing.dataBytes,
|
encThing.dataBytes,
|
||||||
encThing.ivBytes,
|
encThing.ivBytes,
|
||||||
key.encKey
|
key.encKey,
|
||||||
|
"cbc"
|
||||||
);
|
);
|
||||||
|
|
||||||
return result ?? null;
|
return result ?? null;
|
||||||
|
|
|
@ -115,7 +115,8 @@ describe("EncryptService", () => {
|
||||||
expect(cryptoFunctionService.aesDecrypt).toBeCalledWith(
|
expect(cryptoFunctionService.aesDecrypt).toBeCalledWith(
|
||||||
expect.toEqualBuffer(encBuffer.dataBytes),
|
expect.toEqualBuffer(encBuffer.dataBytes),
|
||||||
expect.toEqualBuffer(encBuffer.ivBytes),
|
expect.toEqualBuffer(encBuffer.ivBytes),
|
||||||
expect.toEqualBuffer(key.encKey)
|
expect.toEqualBuffer(key.encKey),
|
||||||
|
"cbc"
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(actual).toEqualBuffer(decryptedBytes);
|
expect(actual).toEqualBuffer(decryptedBytes);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Substitute } from "@fluffy-spoon/substitute";
|
||||||
|
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
|
||||||
|
import { DecryptParameters } from "../models/domain/decrypt-parameters";
|
||||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { WebCryptoFunctionService } from "./web-crypto-function.service";
|
import { WebCryptoFunctionService } from "./web-crypto-function.service";
|
||||||
|
@ -233,7 +234,7 @@ describe("WebCrypto Function Service", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("aesEncrypt", () => {
|
describe("aesEncrypt CBC mode", () => {
|
||||||
it("should successfully encrypt data", async () => {
|
it("should successfully encrypt data", async () => {
|
||||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||||
const iv = makeStaticByteArray(16);
|
const iv = makeStaticByteArray(16);
|
||||||
|
@ -254,7 +255,7 @@ describe("WebCrypto Function Service", () => {
|
||||||
const b64Iv = Utils.fromBufferToB64(iv);
|
const b64Iv = Utils.fromBufferToB64(iv);
|
||||||
const symKey = new SymmetricCryptoKey(key);
|
const symKey = new SymmetricCryptoKey(key);
|
||||||
const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey);
|
const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey);
|
||||||
const decValue = await cryptoFunctionService.aesDecryptFast(params);
|
const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc");
|
||||||
expect(decValue).toBe(value);
|
expect(decValue).toBe(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -265,30 +266,53 @@ describe("WebCrypto Function Service", () => {
|
||||||
const value = "EncryptMe!";
|
const value = "EncryptMe!";
|
||||||
const data = Utils.fromUtf8ToArray(value);
|
const data = Utils.fromUtf8ToArray(value);
|
||||||
const encValue = new Uint8Array(await cryptoFunctionService.aesEncrypt(data, iv, key));
|
const encValue = new Uint8Array(await cryptoFunctionService.aesEncrypt(data, iv, key));
|
||||||
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv, key);
|
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv, key, "cbc");
|
||||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("aesDecryptFast", () => {
|
describe("aesDecryptFast CBC mode", () => {
|
||||||
it("should successfully decrypt data", async () => {
|
it("should successfully decrypt data", async () => {
|
||||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||||
const iv = Utils.fromBufferToB64(makeStaticByteArray(16));
|
const iv = Utils.fromBufferToB64(makeStaticByteArray(16));
|
||||||
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32));
|
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||||
const data = "ByUF8vhyX4ddU9gcooznwA==";
|
const data = "ByUF8vhyX4ddU9gcooznwA==";
|
||||||
const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
||||||
const decValue = await cryptoFunctionService.aesDecryptFast(params);
|
const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc");
|
||||||
expect(decValue).toBe("EncryptMe!");
|
expect(decValue).toBe("EncryptMe!");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("aesDecrypt", () => {
|
describe("aesDecryptFast ECB mode", () => {
|
||||||
|
it("should successfully decrypt data", async () => {
|
||||||
|
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||||
|
const key = makeStaticByteArray(32);
|
||||||
|
const data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw==");
|
||||||
|
const params = new DecryptParameters<string>();
|
||||||
|
params.encKey = Utils.fromBufferToByteString(key);
|
||||||
|
params.data = Utils.fromBufferToByteString(data);
|
||||||
|
const decValue = await cryptoFunctionService.aesDecryptFast(params, "ecb");
|
||||||
|
expect(decValue).toBe("EncryptMe!");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("aesDecrypt CBC mode", () => {
|
||||||
it("should successfully decrypt data", async () => {
|
it("should successfully decrypt data", async () => {
|
||||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||||
const iv = makeStaticByteArray(16);
|
const iv = makeStaticByteArray(16);
|
||||||
const key = makeStaticByteArray(32);
|
const key = makeStaticByteArray(32);
|
||||||
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
|
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
|
||||||
const decValue = await cryptoFunctionService.aesDecrypt(data, iv, key);
|
const decValue = await cryptoFunctionService.aesDecrypt(data, iv, key, "cbc");
|
||||||
|
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("aesDecrypt ECB mode", () => {
|
||||||
|
it("should successfully decrypt data", async () => {
|
||||||
|
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||||
|
const key = makeStaticByteArray(32);
|
||||||
|
const data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw==");
|
||||||
|
const decValue = await cryptoFunctionService.aesDecrypt(data, null, key, "ecb");
|
||||||
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -273,17 +273,37 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
aesDecryptFast(parameters: DecryptParameters<string>): Promise<string> {
|
aesDecryptFast(parameters: DecryptParameters<string>, mode: "cbc" | "ecb"): Promise<string> {
|
||||||
const dataBuffer = forge.util.createBuffer(parameters.data);
|
const decipher = (forge as any).cipher.createDecipher(
|
||||||
const decipher = forge.cipher.createDecipher("AES-CBC", parameters.encKey);
|
this.toWebCryptoAesMode(mode),
|
||||||
decipher.start({ iv: parameters.iv });
|
parameters.encKey
|
||||||
|
);
|
||||||
|
const options = {} as any;
|
||||||
|
if (mode === "cbc") {
|
||||||
|
options.iv = parameters.iv;
|
||||||
|
}
|
||||||
|
const dataBuffer = (forge as any).util.createBuffer(parameters.data);
|
||||||
|
decipher.start(options);
|
||||||
decipher.update(dataBuffer);
|
decipher.update(dataBuffer);
|
||||||
decipher.finish();
|
decipher.finish();
|
||||||
const val = decipher.output.toString();
|
const val = decipher.output.toString();
|
||||||
return Promise.resolve(val);
|
return Promise.resolve(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
async aesDecrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
|
async aesDecrypt(
|
||||||
|
data: Uint8Array,
|
||||||
|
iv: Uint8Array,
|
||||||
|
key: Uint8Array,
|
||||||
|
mode: "cbc" | "ecb"
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
if (mode === "ecb") {
|
||||||
|
// Web crypto does not support AES-ECB mode, so we need to do this in forge.
|
||||||
|
const params = new DecryptParameters<string>();
|
||||||
|
params.data = this.toByteString(data);
|
||||||
|
params.encKey = this.toByteString(key);
|
||||||
|
const result = await this.aesDecryptFast(params, "ecb");
|
||||||
|
return Utils.fromByteStringToArray(result);
|
||||||
|
}
|
||||||
const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
|
const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
|
||||||
"decrypt",
|
"decrypt",
|
||||||
]);
|
]);
|
||||||
|
@ -411,6 +431,10 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||||
return algorithm === "sha1" ? "SHA-1" : algorithm === "sha256" ? "SHA-256" : "SHA-512";
|
return algorithm === "sha1" ? "SHA-1" : algorithm === "sha256" ? "SHA-256" : "SHA-512";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toWebCryptoAesMode(mode: "cbc" | "ecb"): string {
|
||||||
|
return mode === "cbc" ? "AES-CBC" : "AES-ECB";
|
||||||
|
}
|
||||||
|
|
||||||
// ref: https://stackoverflow.com/a/47880734/1090359
|
// ref: https://stackoverflow.com/a/47880734/1090359
|
||||||
private checkIfWasmSupported(): boolean {
|
private checkIfWasmSupported(): boolean {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { DecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { NodeCryptoFunctionService } from "./node-crypto-function.service";
|
import { NodeCryptoFunctionService } from "./node-crypto-function.service";
|
||||||
|
@ -164,7 +165,7 @@ describe("NodeCrypto Function Service", () => {
|
||||||
testCompare(true);
|
testCompare(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("aesEncrypt", () => {
|
describe("aesEncrypt CBC mode", () => {
|
||||||
it("should successfully encrypt data", async () => {
|
it("should successfully encrypt data", async () => {
|
||||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
const iv = makeStaticByteArray(16);
|
const iv = makeStaticByteArray(16);
|
||||||
|
@ -181,30 +182,51 @@ describe("NodeCrypto Function Service", () => {
|
||||||
const value = "EncryptMe!";
|
const value = "EncryptMe!";
|
||||||
const data = Utils.fromUtf8ToArray(value);
|
const data = Utils.fromUtf8ToArray(value);
|
||||||
const encValue = await nodeCryptoFunctionService.aesEncrypt(data, iv, key);
|
const encValue = await nodeCryptoFunctionService.aesEncrypt(data, iv, key);
|
||||||
const decValue = await nodeCryptoFunctionService.aesDecrypt(encValue, iv, key);
|
const decValue = await nodeCryptoFunctionService.aesDecrypt(encValue, iv, key, "cbc");
|
||||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("aesDecryptFast", () => {
|
describe("aesDecryptFast CBC mode", () => {
|
||||||
it("should successfully decrypt data", async () => {
|
it("should successfully decrypt data", async () => {
|
||||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
const iv = Utils.fromBufferToB64(makeStaticByteArray(16));
|
const iv = Utils.fromBufferToB64(makeStaticByteArray(16));
|
||||||
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32));
|
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||||
const data = "ByUF8vhyX4ddU9gcooznwA==";
|
const data = "ByUF8vhyX4ddU9gcooznwA==";
|
||||||
const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
||||||
const decValue = await nodeCryptoFunctionService.aesDecryptFast(params);
|
const decValue = await nodeCryptoFunctionService.aesDecryptFast(params, "cbc");
|
||||||
expect(decValue).toBe("EncryptMe!");
|
expect(decValue).toBe("EncryptMe!");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("aesDecrypt", () => {
|
describe("aesDecryptFast ECB mode", () => {
|
||||||
|
it("should successfully decrypt data", async () => {
|
||||||
|
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
|
const params = new DecryptParameters<Uint8Array>();
|
||||||
|
params.encKey = makeStaticByteArray(32);
|
||||||
|
params.data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw==");
|
||||||
|
const decValue = await nodeCryptoFunctionService.aesDecryptFast(params, "ecb");
|
||||||
|
expect(decValue).toBe("EncryptMe!");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("aesDecrypt CBC mode", () => {
|
||||||
it("should successfully decrypt data", async () => {
|
it("should successfully decrypt data", async () => {
|
||||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
const iv = makeStaticByteArray(16);
|
const iv = makeStaticByteArray(16);
|
||||||
const key = makeStaticByteArray(32);
|
const key = makeStaticByteArray(32);
|
||||||
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
|
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
|
||||||
const decValue = await nodeCryptoFunctionService.aesDecrypt(data, iv, key);
|
const decValue = await nodeCryptoFunctionService.aesDecrypt(data, iv, key, "cbc");
|
||||||
|
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("aesDecrypt ECB mode", () => {
|
||||||
|
it("should successfully decrypt data", async () => {
|
||||||
|
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
|
const key = makeStaticByteArray(32);
|
||||||
|
const data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw==");
|
||||||
|
const decValue = await nodeCryptoFunctionService.aesDecrypt(data, null, key, "ecb");
|
||||||
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -189,16 +189,24 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
async aesDecryptFast(parameters: DecryptParameters<Uint8Array>): Promise<string> {
|
async aesDecryptFast(
|
||||||
const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey);
|
parameters: DecryptParameters<Uint8Array>,
|
||||||
|
mode: "cbc" | "ecb"
|
||||||
|
): Promise<string> {
|
||||||
|
const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey, mode);
|
||||||
return Utils.fromBufferToUtf8(decBuf);
|
return Utils.fromBufferToUtf8(decBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
aesDecrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
|
aesDecrypt(
|
||||||
|
data: Uint8Array,
|
||||||
|
iv: Uint8Array,
|
||||||
|
key: Uint8Array,
|
||||||
|
mode: "cbc" | "ecb"
|
||||||
|
): Promise<Uint8Array> {
|
||||||
const nodeData = this.toNodeBuffer(data);
|
const nodeData = this.toNodeBuffer(data);
|
||||||
const nodeIv = this.toNodeBuffer(iv);
|
const nodeIv = mode === "ecb" ? null : this.toNodeBuffer(iv);
|
||||||
const nodeKey = this.toNodeBuffer(key);
|
const nodeKey = this.toNodeBuffer(key);
|
||||||
const decipher = crypto.createDecipheriv("aes-256-cbc", nodeKey, nodeIv);
|
const decipher = crypto.createDecipheriv(this.toNodeCryptoAesMode(mode), nodeKey, nodeIv);
|
||||||
const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]);
|
const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]);
|
||||||
return Promise.resolve(this.toUint8Buffer(decBuf));
|
return Promise.resolve(this.toUint8Buffer(decBuf));
|
||||||
}
|
}
|
||||||
|
@ -326,4 +334,8 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||||
const publicKey = forge.pki.publicKeyFromAsn1(asn1);
|
const publicKey = forge.pki.publicKeyFromAsn1(asn1);
|
||||||
return forge.pki.publicKeyToPem(publicKey);
|
return forge.pki.publicKeyToPem(publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toNodeCryptoAesMode(mode: "cbc" | "ecb"): string {
|
||||||
|
return mode === "cbc" ? "aes-256-cbc" : "aes-256-ecb";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue