Releases: zeruniverse/Password-Manager
15.01 Release
Updates:
- Add a mechanism to avoid duplicated IVs
To upgrade from v15.00, just download release zip and overwrite code. Database format is not changed.
15.00 Release
Breaking changes from 11.xx / 13.xx
- Algorithm changed from AES-CBC to AES-GCM with username used as AAD
- Backup without username problem fixed
- Fixed QR code not able to import.
To upgrade from 11.xx or 13.xx, follow these steps:
- Generate
backup.txtfrom your existing deployment. - Check if key
useris inbackup.txt, if not, please manually add it to the JSON, it should be"user": " <your_login_username>. Due to a bug, some11.xx/13.xxversions will generate backup files withoutuserkey. - Go to recovery page of your existing deployment and recover the
backup.txt, then export raw. - Deploy the
15.00version and truncate all tables in the database. (if you disabled signup, you might have to temporary enable it now) - Sign-up in the new deployment, and import the raw file.
- Very important: delete the raw file.
I don't plan to make any more breaking changes (i.e. require export / import) in at least next two years.
13.01 Release
- Fixed hotkey
a
You have to first upgrade to 13.00. The 13.00 version includes breaking changes.
13.00 Release
ALL USERS ARE REQUIRED TO UPGRADE TO THIS VERSION!
One minor security flaw has been observed in 11.xx versions. It has been fixed in bbb83cd If you use strong master password, this issue shouldn't impact safety of your password. But you should upgrade as soon as possible.
This upgrade is not backward compatible, you can either export / clear database / re-import, or use following:
- Back up your password. This is very important in case your upgrade fails.
- Upgrade to 11.09 and login to your 11.09 password manager. If you are now at 11.xx, upgrading to 11.09 does not require database migration.
- After login into the 11.09 password manager (where you can see all password entries), open browser developer tool and go to console, paste following scripts and run.
(async function migratePromiseSaltBugToFixedKdf() {
"use strict";
/*
* Run this on the OLD/BROKEN deployment, after the user has logged in
* and the vault page has loaded.
*
* It migrates the vault from:
* legacy secretkey = SgenerateKeyWithSalt(reduceInfo(password), PromiseObject)
*
* to:
* fixedDerivedSalt = WgenerateKeyWithSalt(user, globalSalt1)
* fixed secretkey = SgenerateKeyWithSalt(reduceInfo(password), fixedDerivedSalt)
*
* It keeps the same master password.
*/
function fail(message) {
throw new Error("[PM KDF migration] " + message);
}
function assertRuntime() {
if (typeof EncryptionWrapper !== "function") {
fail("EncryptionWrapper is not available. Run this on the password manager frontend page.");
}
if (typeof AccountBackend !== "function") {
fail("AccountBackend is not available. Run this on the logged-in vault page.");
}
if (typeof pmBackendConfig !== "function") {
fail("pmBackendConfig is not available. frontend_routes.js may not be loaded.");
}
if (typeof PBKDF2_SHA512 !== "function" || typeof SHA512 !== "function") {
fail("crypto helpers are not available.");
}
}
async function fixedGenerateSecretKey(wrapper, password, user) {
const derivedSalt = await EncryptionWrapper.WgenerateKeyWithSalt(
user,
wrapper.jsSalt
);
return EncryptionWrapper.SgenerateKeyWithSalt(
EncryptionWrapper.reduceInfo(password, wrapper.alphabet),
derivedSalt
);
}
function clearLocalPinStateOnly() {
/*
* changeuserpw.php deletes server-side PIN records.
* Clear local PIN material too, otherwise old PIN login state is stale.
* Keep username cookie because it is harmless/useful for normal login.
*/
try {
localStorage.removeItem("pinsalt");
localStorage.removeItem("en_login_sec");
localStorage.removeItem("en_login_conf");
if (typeof deleteCookie === "function") {
deleteCookie("device");
}
} catch (err) {
console.warn("[PM KDF migration] Could not clear local PIN state:", err);
}
}
assertRuntime();
const masterPassword = window.prompt(
"Enter the CURRENT master password for this account. It will not be printed to console."
);
if (masterPassword === null || masterPassword === "") {
fail("Cancelled or empty password.");
}
console.log("[PM KDF migration] 1/7 Loading and decrypting current vault...");
const backend = new AccountBackend();
await backend.loadAccounts();
const user =
backend.user ||
(typeof getCookie === "function" ? getCookie("username") : "");
if (!user) {
fail("Could not determine current username.");
}
if (!backend.encryptionWrapper || !backend.encryptionWrapper.secretkey) {
fail("Current vault secret key is missing. Are you logged in?");
}
const oldWrapper = backend.encryptionWrapper;
const oldSecretKey = oldWrapper.secretkey;
console.log("[PM KDF migration] 2/7 Verifying master password against legacy KDF...");
const legacySecretKey = await oldWrapper.generateSecretKey(
masterPassword,
user,
false
);
if (legacySecretKey !== oldSecretKey) {
fail("Master password did not match the currently loaded legacy vault key.");
}
console.log("[PM KDF migration] 3/7 Deriving fixed KDF secret key...");
const fixedSecretKey = await fixedGenerateSecretKey(
oldWrapper,
masterPassword,
user
);
if (!fixedSecretKey || fixedSecretKey === oldSecretKey) {
fail("Fixed secret key derivation failed or unexpectedly matched the legacy key.");
}
const fixedLoginSignaturePromise = EncryptionWrapper.WgenerateKeyWithSalt(
fixedSecretKey,
user
);
const fixedConfKeyPromise = EncryptionWrapper.WgenerateKeyWithSalt(
masterPassword,
fixedSecretKey
);
const fixedResults = await Promise.all([
fixedLoginSignaturePromise,
fixedConfKeyPromise
]);
const fixedLoginSignature = fixedResults[0];
const fixedConfKey = fixedResults[1];
const fixedWrapper = new EncryptionWrapper(
fixedSecretKey,
oldWrapper.jsSalt,
oldWrapper.pwSalt,
oldWrapper.alphabet
);
fixedWrapper._confkey = fixedConfKey;
console.log("[PM KDF migration] 4/7 Re-encrypting accounts and file keys...");
const encryptedAccountPromises = [];
for (const account of backend.accounts) {
if (!account) {
continue;
}
encryptedAccountPromises.push(
account
.setEncryptionWrapper(fixedWrapper)
.then(function (updatedAccount) {
return updatedAccount.getEncrypted(true);
})
);
}
const encryptedAccounts = await Promise.all(encryptedAccountPromises);
const accarray = [];
for (const encryptedAccount of encryptedAccounts) {
if (
!encryptedAccount ||
typeof encryptedAccount.index === "undefined" ||
encryptedAccount.index === null
) {
fail("Encountered an encrypted account without index.");
}
accarray[encryptedAccount.index] = encryptedAccount;
}
console.log(
"[PM KDF migration] 5/7 Sending migrated vault to server. Accounts:",
encryptedAccounts.length
);
const response = await backend.doPost("changeuserpw", {
newpass: fixedLoginSignature,
accarray: JSON.stringify(accarray)
});
console.log("[PM KDF migration] 6/7 Updating local session storage...");
if (response && response.password) {
if (typeof pmSetAuthCredentials === "function") {
pmSetAuthCredentials(user, response.password);
} else {
sessionStorage.pm_auth_user = user;
sessionStorage.pm_auth_password = String(response.password).toLowerCase();
}
}
await EncryptionWrapper.persistCredentials(
user,
fixedSecretKey,
fixedConfKey,
oldWrapper.pwSalt
);
clearLocalPinStateOnly();
try {
if (typeof backend.clearTimeout === "function") {
backend.clearTimeout();
}
} catch (err) {
console.warn("[PM KDF migration] Could not clear backend timers:", err);
}
console.log("[PM KDF migration] 7/7 DONE.");
console.log(
"[PM KDF migration] Now deploy the fixed frontend code, hard-refresh the page, and log in with the SAME master password. Re-create PIN login afterward if you use PIN."
);
return {
status: "success",
user: user,
migratedAccounts: encryptedAccounts.length
};
})().catch(function (err) {
console.error(err);
alert(String(err && err.message ? err.message : err));
});- Wait for
DONEoutput in console - Logout and deploy
13.00. At this point, you can no longer login in11.xx. - Clear all cache, session storage and local storage, login into Password Manager 13.00. If the console fix scripts succeed, you should be able to login and see all your old data.
11.09 Release
Updates:
- Make backend stateless to support serverless deployment.
- Add _headers to frontend to ensure safety
To upgrade from v11.00 or above, just download release zip and overwrite code. Database format is not changed.
11.08 Release
Updates:
- Removed E-mail based 2FA for Password-Manager master account
- Added support for TOTP based 2FA for Password-Manager master account (This is not client-side encryption, if you lose your phone, you can modify data in database to re-gain access)
- Split frontend and backend code so they can be separately deployed. This adds an additional layer of security compared to old integrity check. Be sure to deploy frontend code to somewhere absolutely safe (same as old integrity check html). You can use GitHub Pages or Cloudflare Pages and be sure that your account associated with the static deployment is secure (e.g. enable 2FA and use strong password)
To upgrade from v11.00 or above, just download release zip and overwrite code. Database format is not changed.
11.07 Release
Clean up of docs.
11.06 Release
Updates:
- Use secure random
- Support management of TOTP MFA
To upgrade from v11.00 or above, just download release zip and overwrite code. Database format is not changed.
11.05 Release
Updates:
- Fix unexpected session timeout
- Change all filename to lower-case so it's compatible with case-insensitive web servers
- Allow copy password regardless of whether it's shown.
- Support for ipv6
- Fix history log for servers behind WAF or CDN
To upgrade from v11.00 or above, just download release zip and overwrite code. Database format is not changed.
11.01 Release
Email verification service changed from SendGrid to Gmail. I received complaints about SendGrid not friendly to individual users and thus I changed default / sample email verification method to Gmail. You only need a valid Gmail account to make email verification work. Just set up 2-step verification and generate an app-specific password for this password manager.
Except for above email verification change, this release is same with 11.00. So to upgrade from 11.00, you just need to overwrite source code files.