diff --git a/package.json b/package.json index e901660..e0ca088 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,12 @@ "aws-crt": "^1.10.1", "buffer": "^6.0.3", "codemirror": "^5.63.1", - "hi-base32": "^0.5.1", "lovefield-ts": "^0.7.0", "mime-types": "^2.1.33", "obsidian": "^0.12.0", "path-browserify": "^1.0.1", "process": "^0.11.10", + "rfc4648": "^1.5.0", "rimraf": "^3.0.2", "stream-browserify": "^3.0.0", "webdav": "^4.7.0", diff --git a/src/encrypt.ts b/src/encrypt.ts index 50d6e64..19f815c 100644 --- a/src/encrypt.ts +++ b/src/encrypt.ts @@ -1,5 +1,10 @@ -import * as base32 from "hi-base32"; -import { bufferToArrayBuffer, arrayBufferToBuffer } from "./misc"; +import { base32, base64 } from "rfc4648"; +import { + bufferToArrayBuffer, + arrayBufferToBuffer, + hexStringToTypedArray, + arrayBufferToHex, +} from "./misc"; const DEFAULT_ITER = 10000; @@ -33,9 +38,15 @@ const getKeyIVFromPassword = async ( export const encryptArrayBuffer = async ( arrBuf: ArrayBuffer, password: string, - rounds: number = DEFAULT_ITER + rounds: number = DEFAULT_ITER, + saltHex: string = "" ) => { - const salt = window.crypto.getRandomValues(new Uint8Array(8)); + let salt: Uint8Array; + if (saltHex !== "") { + salt = hexStringToTypedArray(saltHex); + } else { + salt = window.crypto.getRandomValues(new Uint8Array(8)); + } const derivedKey = await getKeyIVFromPassword(salt, password, rounds); const key = derivedKey.slice(0, 32); @@ -97,11 +108,16 @@ export const decryptArrayBuffer = async ( export const encryptStringToBase32 = async ( text: string, password: string, - rounds: number = DEFAULT_ITER + rounds: number = DEFAULT_ITER, + saltHex: string = "" ) => { - return base32.encode( - await encryptArrayBuffer(new TextEncoder().encode(text), password, rounds) + const enc = await encryptArrayBuffer( + bufferToArrayBuffer(new TextEncoder().encode(text)), + password, + rounds, + saltHex ); + return base32.stringify(new Uint8Array(enc)); }; export const decryptBase32ToString = async ( @@ -109,11 +125,11 @@ export const decryptBase32ToString = async ( password: string, rounds: number = DEFAULT_ITER ) => { - return ( + return new TextDecoder().decode( await decryptArrayBuffer( - bufferToArrayBuffer(Uint8Array.from(base32.decode.asBytes(text))), + bufferToArrayBuffer(base32.parse(text)), password, rounds ) - ).toString(); + ); }; diff --git a/src/misc.ts b/src/misc.ts index ae2169e..1d1a7a7 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -1,6 +1,8 @@ import { Vault } from "obsidian"; import * as path from "path"; +import { base32 } from "rfc4648"; + export type SUPPORTED_SERVICES_TYPE = "s3" | "webdav" | "ftp"; export const ignoreHiddenFiles = (item: string) => { @@ -63,6 +65,27 @@ export const arrayBufferToBase64 = (b: ArrayBuffer) => { return arrayBufferToBuffer(b).toString("base64"); }; +export const arrayBufferToHex = (b: ArrayBuffer) => { + return arrayBufferToBuffer(b).toString("hex"); +}; + export const base64ToArrayBuffer = (b64text: string) => { return bufferToArrayBuffer(Buffer.from(b64text, "base64")); }; + +/** + * https://stackoverflow.com/questions/43131242 + * @param hex + * @returns + */ +export const hexStringToTypedArray = (hex: string) => { + return new Uint8Array( + hex.match(/[\da-f]{2}/gi).map(function (h) { + return parseInt(h, 16); + }) + ); +}; + +export const base64ToBase32 = (a: string) => { + return base32.stringify(Buffer.from(a, "base64")); +}; diff --git a/tests/sometext.txt b/tests/sometext.txt new file mode 100644 index 0000000..1d53ee3 --- /dev/null +++ b/tests/sometext.txt @@ -0,0 +1 @@ +A secret text 你好世界 diff --git a/tests/test-encrypt.ts b/tests/test-encrypt.ts index bfcc070..3b8cefb 100644 --- a/tests/test-encrypt.ts +++ b/tests/test-encrypt.ts @@ -1,5 +1,10 @@ +import * as fs from "fs"; import { expect } from "chai"; -import { encryptStringToBase32 } from "../src/encrypt"; +import { base64ToBase32 } from "../src/misc"; +import { + decryptBase32ToString, + encryptStringToBase32, +} from "../src/encrypt"; describe("Encryption tests", () => { beforeEach(function () { @@ -13,4 +18,38 @@ describe("Encryption tests", () => { const password = "hey"; expect(await encryptStringToBase32(k, password)).to.not.equal(k); }); + + it("should encrypt and decrypt string and get the same result returned", async () => { + const k = "jfkkjkjbce7983ycdeknkkjckooAIUHIDIBIE((*BII)njD/d/dd/d/sjxhux"; + const password = "hfiuibec989###oiu982bj1`"; + const enc = await encryptStringToBase32(k, password); + console.log(enc); + const dec = await decryptBase32ToString(enc, password); + console.log(dec); + expect(dec).equal(k); + }); + + it("should encrypt and get the same result as openssl", async () => { + const fileContent = ( + await fs.readFileSync(__dirname + "/sometext.txt") + ).toString("utf-8"); + const password = "somepassword"; + const saltHex = "8302F586FAB491EC"; + const enc = await encryptStringToBase32( + fileContent, + password, + undefined, + saltHex + ); + + // two command returns same result: + // cat ./sometext.txt | openssl enc -p -aes-256-cbc -S 8302F586FAB491EC -pbkdf2 -iter 10000 -base64 -pass pass:somepassword + // openssl enc -p -aes-256-cbc -S 8302F586FAB491EC -pbkdf2 -iter 10000 -base64 -pass pass:somepassword -in ./sometext.txt + const opensslBase64Res = + "U2FsdGVkX1+DAvWG+rSR7MSa+yJav1zCE7SSXiBooqwI5Q+LMpIthpk/pXkLj+25"; + // we output base32, so we need some transformation + const opensslBase32Res = base64ToBase32(opensslBase64Res); + + expect(enc).equal(opensslBase32Res); + }); });