Skip to content

feat: add password salting with SHA-256 + random salt - #1082

Open
daoischain-bot wants to merge 1 commit into
dreamhunter2333:mainfrom
daoischain-bot:feat/password-salt
Open

feat: add password salting with SHA-256 + random salt#1082
daoischain-bot wants to merge 1 commit into
dreamhunter2333:mainfrom
daoischain-bot:feat/password-salt

Conversation

@daoischain-bot

@daoischain-bot daoischain-bot commented Jul 10, 2026

Copy link
Copy Markdown

Summary

Adds password salting for stored passwords in both users and address tables using SHA-256 + random UUID salt.

Changes

  • utils.ts: Added hashPasswordWithSalt(password, salt?) and verifyPassword(incomingHash, storedHash, salt)
  • Migration (db/2026-07-10-password-salt.sql): Adds nullable password_salt TEXT column to users and address tables
  • User auth: Registration, login, admin reset — all use salted hashes
  • Address auth: Set password, login — all use salted hashes
  • Backward compatible: verifyPassword falls back to direct SHA-256 comparison when salt is null, so existing records continue to work

Notes

  • Requires nodejs_compat flag in wrangler.toml (for crypto.randomUUID())
  • Existing users/addresses get salted hash on next password change

Summary by CodeRabbit

  • 新功能
    • 密码存储与验证全面支持随机盐哈希,提升账户安全性。
    • 注册、登录、修改密码及管理员重置密码流程已同步支持加盐密码处理。
    • 保留对旧密码格式的兼容验证,便于平滑过渡。

- Add hashPasswordWithSalt / verifyPassword to utils.ts
- Add password_salt column to address and users tables
- Update user registration, login, password change to use salted hash
- Update admin user/address password reset to use salted hash
- Auto-generated address passwords now use salted hash
- Backward compatible: verifyPassword falls back to direct comparison when salt is null
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

新增 password_salt 数据库字段和加盐密码工具,并将用户、地址的密码写入及登录校验流程统一接入 SHA-256 加盐处理,同时保留无盐密码的兼容校验。

Changes

密码加盐流程

Layer / File(s) Summary
盐值字段与密码工具
db/2026-07-10-password-salt.sql, worker/src/utils.ts
usersaddress 增加 password_salt,并新增加盐哈希、校验及默认导出。
用户与管理员密码写入
worker/src/user_api/user.ts, worker/src/admin_api/admin_user_api.ts
用户注册及管理员创建、重置密码时生成并保存 hash 与 salt。
地址密码写入流程
worker/src/admin_api/address_api.ts, worker/src/common.ts, worker/src/mails_api/address_auth.ts
地址重置、生成和修改密码时同时保存 passwordpassword_salt
加盐密码登录校验
worker/src/user_api/user.ts, worker/src/mails_api/address_auth.ts
登录查询盐值,并通过 verifyPassword 支持加盐及 legacy 密码校验。

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthAPI
  participant PasswordUtils
  participant Database

  Client->>AuthAPI: 注册或修改密码
  AuthAPI->>PasswordUtils: hashPasswordWithSalt(password)
  PasswordUtils-->>AuthAPI: hash, salt
  AuthAPI->>Database: 保存 password 和 password_salt

  Client->>AuthAPI: 登录
  AuthAPI->>Database: 查询 password 和 password_salt
  AuthAPI->>PasswordUtils: verifyPassword(input, storedHash, salt)
  PasswordUtils-->>AuthAPI: 校验结果
  AuthAPI-->>Client: 登录响应
Loading

Poem

小兔挥铲种盐香,
哈希绕着密码藏。
登录寻盐验真章,
新旧路径都照常。
耳朵一竖,安全长!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了本次变更:为密码存储引入 SHA-256 加随机盐。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
worker/src/utils.ts (1)

485-496: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

建议使用恒定时间比较

hash === storedHash(及 legacy 分支的 ===)为非恒定时间比较,存在时序侧信道风险。Cloudflare 最佳实践建议对秘密值使用 crypto.subtle.timingSafeEqual()(先将两侧编码为等长缓冲区,避免按长度短路)。此处比较的是哈希值、可利用性较低,但作为认证路径的安全卫生仍建议改用恒定时间比较。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/src/utils.ts` around lines 485 - 496, 在 verifyPassword
中将哈希比较改为恒定时间比较:使用 crypto.subtle.timingSafeEqual() 比较两侧经过统一编码且长度一致的字节缓冲区,覆盖带盐分支的
hash 与 storedHash 以及 legacy 分支的 incomingHash 与 storedHash;长度不一致时先安全返回
false,避免直接使用 ===。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@worker/src/utils.ts`:
- Around line 470-479: Replace the single SHA-256 implementation in
hashPasswordWithSalt with Web Crypto PBKDF2 using crypto.subtle.importKey and
deriveBits, a cryptographically random salt, and at least 100,000 iterations;
update the output format to preserve the algorithm parameters. Extend
verifyPassword to recognize and validate existing SHA-256 hashes, then rehash
and persist them with PBKDF2 after successful login.

---

Nitpick comments:
In `@worker/src/utils.ts`:
- Around line 485-496: 在 verifyPassword 中将哈希比较改为恒定时间比较:使用
crypto.subtle.timingSafeEqual() 比较两侧经过统一编码且长度一致的字节缓冲区,覆盖带盐分支的 hash 与 storedHash
以及 legacy 分支的 incomingHash 与 storedHash;长度不一致时先安全返回 false,避免直接使用 ===。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9136569b-2581-4213-8a3a-5ce030b1a35a

📥 Commits

Reviewing files that changed from the base of the PR and between 565bb83 and 3ed0092.

📒 Files selected for processing (7)
  • db/2026-07-10-password-salt.sql
  • worker/src/admin_api/address_api.ts
  • worker/src/admin_api/admin_user_api.ts
  • worker/src/common.ts
  • worker/src/mails_api/address_auth.ts
  • worker/src/user_api/user.ts
  • worker/src/utils.ts

Comment thread worker/src/utils.ts
Comment on lines +470 to +479
export const hashPasswordWithSalt = async (
password: string,
salt?: string
): Promise<{ hash: string, salt: string }> => {
const useSalt = salt || crypto.randomUUID();
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password + useSalt));
const hashArray = Array.from(new Uint8Array(digest));
const hash = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
return { hash, salt: useSalt };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

Cloudflare Workers WebCrypto PBKDF2 deriveBits password hashing example

💡 Result:

Cloudflare Workers fully support the standard Web Crypto API, which is the recommended way to perform PBKDF2 password hashing [1][2]. Because the Workers runtime implements the standard SubtleCrypto interface, the process follows a two-step asynchronous pattern [2][3]. ### Implementation Steps 1. Import the Password: Use crypto.subtle.importKey to convert your raw password string (encoded as a Uint8Array) into a cryptographic key object [4][3]. 2. Derive Bits: Use crypto.subtle.deriveBits with the PBKDF2 algorithm parameters to perform the hashing [4][2]. ### Code Example The following example demonstrates how to derive bits from a password using PBKDF2: async function derivePasswordHash(password, salt) { const encoder = new TextEncoder; const passwordData = encoder.encode(password); // 1. Import the password as a key material object const keyMaterial = await crypto.subtle.importKey( "raw", passwordData, { name: "PBKDF2" }, false, ["deriveBits"]); // 2. Derive bits using PBKDF2 const derivedBits = await crypto.subtle.deriveBits( { name: "PBKDF2", salt: salt, // Must be a Uint8Array, preferably cryptographically random iterations: 100000, // Adjust based on security requirements hash: "SHA-256" }, keyMaterial, 256 // Length of the derived bits in bits); return new Uint8Array(derivedBits); } // Helper: Generating a random salt const salt = crypto.getRandomValues(new Uint8Array(16)); ### Best Practices * Salt: Always use a unique, cryptographically random salt for every password [2]. Generate it using crypto.getRandomValues() [2]. * Iterations: Use a high iteration count (e.g., 100,000 or more) to protect against brute-force attacks [4][2]. * Asynchronous Handling: Since Web Crypto API methods are asynchronous, ensure you await the results to avoid blocking the event loop [3]. * Output Conversion: The result of deriveBits is an ArrayBuffer. If you need to store it as a string (e.g., for a database), convert it to a hexadecimal or Base64 format [2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and inspect the relevant section with line numbers.
wc -l worker/src/utils.ts
sed -n '430,520p' worker/src/utils.ts | cat -n

# Search for other password hashing / verification helpers.
rg -n "hashPasswordWithSalt|verifyPassword|PBKDF2|scrypt|bcrypt|argon2|subtle\.deriveBits|crypto\.subtle\.digest\('SHA-256'" worker/src -S

Repository: dreamhunter2333/cloudflare_temp_email

Length of output: 6602


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the current file context around the helper and its callers/usages.
rg -n "hashPasswordWithSalt|verifyPassword" worker/src -S

Repository: dreamhunter2333/cloudflare_temp_email

Length of output: 2488


单轮 SHA-256 不适合作为密码哈希

Workers 原生支持 Web Crypto 的 PBKDF2,建议改成 crypto.subtle.importKey + deriveBits,使用随机盐和至少 100,000 次迭代;当前实现只能防彩虹表,挡不住弱口令的离线爆破。旧数据可在 verifyPassword 里按格式兼容,并在登录后逐步升级。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/src/utils.ts` around lines 470 - 479, Replace the single SHA-256
implementation in hashPasswordWithSalt with Web Crypto PBKDF2 using
crypto.subtle.importKey and deriveBits, a cryptographically random salt, and at
least 100,000 iterations; update the output format to preserve the algorithm
parameters. Extend verifyPassword to recognize and validate existing SHA-256
hashes, then rehash and persist them with PBKDF2 after successful login.

@dreamhunter2333

Copy link
Copy Markdown
Owner

Thanks for working on this. Before this can be merged, please take one of the following approaches:

  1. Fully integrate the schema change: update db/schema.sql and DB_INIT_QUERIES in worker/src/admin_api/db_api.ts, bump DB_VERSION, add guarded migrations for both address and users, update both changelogs and any relevant deployment docs, and make E2E pass. The current standalone SQL patch is not enough; E2E currently fails with no such column: password_salt.

  2. Avoid a schema change: store a self-describing verifier in the existing password TEXT field, for example pbkdf2-sha256$<iterations>$<salt>$<digest>. Parse that format in verifyPassword and fall back to the legacy 64-character SHA-256 value. This preserves backward compatibility without requiring a D1 migration.

I prefer option 2 because it keeps deployments backward-compatible and stores the algorithm, version, cost factor, salt, and digest together. Please also use a slow password KDF such as PBKDF2 rather than a single SHA-256 round.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants