use base64url not base32

This commit is contained in:
fyears 2021-12-31 01:00:52 +08:00
parent 792703f8b6
commit 1bc6ba4b01
3 changed files with 84 additions and 7 deletions

View File

@ -1,4 +1,4 @@
import { base32, base64 } from "rfc4648"; import { base32, base64url } from "rfc4648";
import { import {
bufferToArrayBuffer, bufferToArrayBuffer,
arrayBufferToBuffer, arrayBufferToBuffer,
@ -10,6 +10,8 @@ const DEFAULT_ITER = 20000;
// base32.stringify(Buffer.from('Salted__')) // base32.stringify(Buffer.from('Salted__'))
export const MAGIC_ENCRYPTED_PREFIX_BASE32 = "KNQWY5DFMRPV"; export const MAGIC_ENCRYPTED_PREFIX_BASE32 = "KNQWY5DFMRPV";
// base64.stringify(Buffer.from('Salted__'))
export const MAGIC_ENCRYPTED_PREFIX_BASE64URL = "U2FsdGVkX";
const getKeyIVFromPassword = async ( const getKeyIVFromPassword = async (
salt: Uint8Array, salt: Uint8Array,
@ -120,7 +122,7 @@ export const encryptStringToBase32 = async (
rounds, rounds,
saltHex saltHex
); );
return base32.stringify(new Uint8Array(enc)); return base32.stringify(new Uint8Array(enc), { pad: false });
}; };
export const decryptBase32ToString = async ( export const decryptBase32ToString = async (
@ -130,7 +132,36 @@ export const decryptBase32ToString = async (
) => { ) => {
return new TextDecoder().decode( return new TextDecoder().decode(
await decryptArrayBuffer( await decryptArrayBuffer(
bufferToArrayBuffer(base32.parse(text)), bufferToArrayBuffer(base32.parse(text, { loose: true })),
password,
rounds
)
);
};
export const encryptStringToBase64url = async (
text: string,
password: string,
rounds: number = DEFAULT_ITER,
saltHex: string = ""
) => {
const enc = await encryptArrayBuffer(
bufferToArrayBuffer(new TextEncoder().encode(text)),
password,
rounds,
saltHex
);
return base64url.stringify(new Uint8Array(enc), { pad: false });
};
export const decryptBase64urlToString = async (
text: string,
password: string,
rounds: number = DEFAULT_ITER
) => {
return new TextDecoder().decode(
await decryptArrayBuffer(
bufferToArrayBuffer(base64url.parse(text, { loose: true })),
password, password,
rounds rounds
) )

View File

@ -1,7 +1,7 @@
import { Vault } from "obsidian"; import { Vault } from "obsidian";
import * as path from "path"; import * as path from "path";
import { base32 } from "rfc4648"; import { base32, base64url } from "rfc4648";
import XRegExp from "xregexp"; import XRegExp from "xregexp";
/** /**

View File

@ -14,6 +14,9 @@ import {
decryptBase32ToString, decryptBase32ToString,
encryptStringToBase32, encryptStringToBase32,
MAGIC_ENCRYPTED_PREFIX_BASE32, MAGIC_ENCRYPTED_PREFIX_BASE32,
decryptBase64urlToString,
encryptStringToBase64url,
MAGIC_ENCRYPTED_PREFIX_BASE64URL,
} from "./encrypt"; } from "./encrypt";
export type SyncStatusType = export type SyncStatusType =
@ -85,7 +88,7 @@ export const isPasswordOk = async (
} }
const santyCheckKey = remote[0].key; const santyCheckKey = remote[0].key;
if (santyCheckKey.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE32)) { if (santyCheckKey.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE32)) {
// this is encrypted! // this is encrypted using old base32!
// try to decrypt it using the provided password. // try to decrypt it using the provided password.
if (password === "") { if (password === "") {
return { return {
@ -96,6 +99,38 @@ export const isPasswordOk = async (
try { try {
const res = await decryptBase32ToString(santyCheckKey, password); const res = await decryptBase32ToString(santyCheckKey, password);
// additional test
// because iOS Safari bypasses decryption with wrong password!
if (isVaildText(res)) {
return {
ok: true,
reason: "password_matched",
} as PasswordCheckType;
} else {
return {
ok: false,
reason: "invalid_text_after_decryption",
} as PasswordCheckType;
}
} catch (error) {
return {
ok: false,
reason: "password_not_matched",
} as PasswordCheckType;
}
}
if (santyCheckKey.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE64URL)) {
// this is encrypted using new base64url!
// try to decrypt it using the provided password.
if (password === "") {
return {
ok: false,
reason: "remote_encrypted_local_no_password",
} as PasswordCheckType;
}
try {
const res = await decryptBase64urlToString(santyCheckKey, password);
// additional test // additional test
// because iOS Safari bypasses decryption with wrong password! // because iOS Safari bypasses decryption with wrong password!
if (isVaildText(res)) { if (isVaildText(res)) {
@ -145,7 +180,15 @@ const ensembleMixedStates = async (
const remoteEncryptedKey = entry.key; const remoteEncryptedKey = entry.key;
let key = remoteEncryptedKey; let key = remoteEncryptedKey;
if (password !== "") { if (password !== "") {
if (remoteEncryptedKey.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE32)) {
key = await decryptBase32ToString(remoteEncryptedKey, password); key = await decryptBase32ToString(remoteEncryptedKey, password);
} else if (
remoteEncryptedKey.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE64URL)
) {
key = await decryptBase64urlToString(remoteEncryptedKey, password);
} else {
throw Error(`unexpected key=${remoteEncryptedKey}`);
}
} }
const backwardMapping = await getSyncMetaMappingByRemoteKey( const backwardMapping = await getSyncMetaMappingByRemoteKey(
remoteType, remoteType,
@ -435,7 +478,10 @@ const dispatchOperationToActual = async (
if (password !== "") { if (password !== "") {
remoteEncryptedKey = state.remote_encrypted_key; remoteEncryptedKey = state.remote_encrypted_key;
if (remoteEncryptedKey === undefined || remoteEncryptedKey === "") { if (remoteEncryptedKey === undefined || remoteEncryptedKey === "") {
remoteEncryptedKey = await encryptStringToBase32(key, password); // the old version uses base32
// remoteEncryptedKey = await encryptStringToBase32(key, password);
// the new version users base64url
remoteEncryptedKey = await encryptStringToBase64url(key, password);
} }
} }