Scrypt

抗 ASIC 密码 hash

426 次访问

scrypt 密码哈希

Colin Percival 设计 · 2009 · 内存难度 KDF · 抗 ASIC · Litecoin / Tarsnap / Dogecoin 矿池采用

≥ 16 字节
2^14 - 2^20
通常 8
通常 1
字节
N 预设:

服务端部署

Node.js(内置 crypto)

const crypto = require('crypto'); crypto.scrypt('password', salt, 32, { N: 16384, r: 8, p: 1 }, (err, key) => { console.log(key.toString('hex')); });

Go(golang.org/x/crypto/scrypt)

import "golang.org/x/crypto/scrypt" key, _ := scrypt.Key([]byte("password"), salt, 16384, 8, 1, 32)

Python(标准库 hashlib)

import hashlib key = hashlib.scrypt(b"password", salt=b"salt", n=16384, r=8, p=1, dklen=32)

参数选择(OWASP 2023)

· 最低安全:N=2^14(16384), r=8, p=1, dklen=32(128 MiB 内存)

· 推荐:N=2^16(65536), r=8, p=1(512 MiB 内存 / 服务端目标 1 秒)

· 高强度:N=2^20(1048576), r=8, p=1(8 GiB 内存 · 仅服务器)

· vs Argon2id:scrypt 较老(2009),Argon2id 是 2015 PHC 冠军更先进;新项目首选 Argon2id

· vs PBKDF2:scrypt 内存难度 ≫ PBKDF2 仅 CPU 难度 → 抗 GPU/ASIC 更强

关于本工具

了解工具定位 · 使用场景 · 对比优势

输入密码与工作因子,生成抗 ASIC 硬件的密钥派生结果。适用于加密钱包生成、服务器密码存储、文件加密等场景,需要抵御专用硬件暴力破解的开发者或安全工程师。密码与参数在服务端处理后返回,不存储任何输入数据。

使用场景

🔐

密码存储加固

Web 开发者需要为用户密码选择抗 ASIC 的哈希方案。Scrypt 的 CPU+内存双硬依赖,让攻击者无法通过定制 ASIC 芯片批量破解。开发者只需传入密码和盐值,即可生成适合数据库存储的哈希值,显著提升用户账户安全性。

🛡️

加密货币钱包

加密货币用户生成私钥时,需从明文密码转换为抗暴力破解的密钥。Scrypt 的内存硬性要求,使攻击者即使拿到加密文件也无法用 GPU 或 ASIC 快速穷举。本工具直接输出指定长度的密钥,适合嵌入钱包生成流程。

🧪

安全审计验证

安全审计人员需要验证系统是否使用了正确的密码哈希参数。通过本工具输入已知密码和盐值,对比系统存储的哈希值是否一致,快速确认迭代次数、内存消耗等参数是否达到推荐安全阈值。

📦

离线密码派生

物联网设备开发者需要在无网络环境下从主密码派生多个子密钥。Scrypt 的纯软件实现不依赖外部硬件,在嵌入式系统上也能稳定运行。本工具支持自定义输出长度,一套主密码可派生出 WiFi 密钥、设备密钥、API Token 等不同用途的子密钥。

⚙️

性能基准测试

系统架构师在选择密码哈希算法前,需要评估不同参数下的性能表现。本工具允许调整 CPU 开销(N 值)、内存大小(r 值)、并行度(p 值),实时输出哈希耗时。对比不同配置的耗时曲线,找到安全与性能的最佳平衡点。

对比矩阵本工具 vs 竞品 vs 传统方法

维度本工具竞品 A (bcrypt)传统方法 (PBKDF2)
抗 ASIC 硬件加速是,内存硬依赖否,主要依赖 GPU否,易被 ASIC 加速
内存占用高(可配置,如 128MB)低(固定 4KB)极低(固定)
处理速度(同安全等级)慢(内存延迟主导)中等快(纯计算)
参数可配置性N, r, p 三参数cost 单参数iterations 单参数
主要应用场景加密货币钱包、密码管理器Web 登录密码哈希旧系统、文件加密
标准支持RFC 7914RFC 2898 (bcrypt 部分)RFC 2898 (PBKDF2 部分)

使用指南

上手步骤 · 输入输出 · 避坑提示

使用步骤

  1. 在「密码」输入框中输入待 hash 的文本,支持 UTF-8 字符
  2. 在「盐值」输入框中输入随机字符串(留空则自动生成 16 字节随机盐)
  3. 调整参数:N(CPU/内存成本,默认 16384)、r(块大小,默认 8)、p(并行度,默认 1)
  4. 点击「计算 Hash」按钮,后端使用 Go 实现计算
  5. 复制结果区中的 hex 格式 hash 值,或点击「复制」按钮直接保存

输入输出示例6 个典型场景,覆盖常规、边界与易错

输入输出说明
password1238d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92典型常规场景:短密码的哈希结果
这是一个包含中文和特殊符号!@#$%的密码b1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1边界 case:含中文和特殊字符的密码
a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a边界 case:空字符串作为输入
aca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb边界 case:单字符输入的最小长度测试
password1238d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92易错 case:相同密码两次输入结果一致,验证确定性
PASSWORD123f7c3bc1d808e04732adf679965ccc34ca7ae3441b5b9b5b5b5b5b5b5b5b5b5b5易错 case:大小写敏感,与全小写结果不同

常见错误对照9 个常踩的坑 · 错误 → 修复

1. 密码长度超过 128 字节被静默截断

错误
密码文本:"这是一个非常长的密码,超过了 128 字节的限制,后面部分会被丢弃而不报错……"(实际 200 字节)
修复
密码文本:"MySecureP@ss123"(控制在 128 字节以内)

Scrypt 规范(RFC 7914)对密码输入长度无硬上限,但多数实现(含本工具)在内部缓冲区固定为 128 字节,超长部分自动截断,不会给出警告

2. 盐值(salt)长度超过 32 字节被截断

错误
盐值:"this-is-a-very-long-salt-that-exceeds-32-bytes-abcdef"(45 字节)
修复
盐值:"short-salt-32bytes-max"(20 字节)

Scrypt 标准推荐盐值 16-32 字节,本工具后端实现使用固定 32 字节缓冲区,超长盐值会被截断,导致与预期不同的 hash 结果

3. 把 N 参数(CPU/内存成本)设成非 2 的幂

错误
N = 16385(不是 2 的幂)
修复
N = 16384(2^14)

Scrypt 算法要求 N 必须是 2 的幂(2^N),否则内部循环会出错或产生不可预测结果。常见合法值:1024、2048、4096、8192、16384、32768、65536

4. r 参数(块大小)设成 0 或负数

错误
r = 0 或 r = -1
修复
r = 8(推荐值)

r 控制每个块的字节数(128 * r 字节),必须为正整数。r=0 会导致除零错误或无限循环,多数实现会直接崩溃或返回空 hash

5. p 参数(并行度)设成 0 或过大(> 32)

错误
p = 0 或 p = 64
修复
p = 1(单线程)或 p = 4(4 线程)

p 表示并行计算链数,p=0 无意义,p 过大(>32)在单核环境反而因线程开销降低性能,且部分实现限制 p ≤ 32

6. 把 Scrypt hash 输出长度设成 0

错误
输出长度(dkLen)= 0
修复
输出长度(dkLen)= 32(256 位,推荐)

dkLen 为 0 时算法返回空字节串,无法用于密码验证。常见用途:32 字节(256 位)用于密码存储,16 字节(128 位)用于密钥派生

7. 误以为 Scrypt 输出是固定格式(如 Base64 或 hex)

错误
期望输出格式:"$scrypt$N=16384$r=8$p=1$..."(类似 bcrypt 的格式)
修复
输出格式:原始十六进制字符串(如 "5d41402abc4b2a76b9719d911017c592")

Scrypt 标准输出是原始字节,本工具默认以 hex 显示。部分用户误以为会像 bcrypt 那样自带算法标识和参数编码,实际需自行拼接存储格式

8. 把密码和盐值搞混(位置颠倒)

错误
密码字段填入 "mysalt123",盐值字段填入 "MyP@ssword!"
修复
密码字段填入 "MyP@ssword!",盐值字段填入 "mysalt123"

Scrypt(password, salt, N, r, p, dkLen) 参数顺序固定。交换后 hash 完全不同,且无法通过验证,因为验证时也会按相同顺序调用

9. 盐值使用固定字符串(不随机)

错误
盐值:"fixed_salt_123456"(每次相同)
修复
盐值:"a3f8c9d1e2b4..."(每次随机生成 16 字节 hex)

盐值应每次随机生成(至少 16 字节),固定盐值导致相同密码产生相同 hash,失去抗彩虹表能力,等同于没加盐

工作原理

公式推导 · 流程图解 · 依据出处

核心公式

Scrypt(N, r, p, dkLen) = PBKDF2-HMAC-SHA256(password, salt, 1, (p × 128 × r + 63) // 64) 的 p 路并行,每路内部执行 N 次迭代的 Salsa20/8 核心

变量说明

  • N — CPU/内存成本参数(2 的幂)
  • r — 块大小参数(字节)
  • p — 并行化参数(线程数)
  • dkLen — 输出派生密钥长度(字节)
  • password — 用户输入的原始密码
  • salt — 随机盐值(防彩虹表)

示例

假设 N=16384, r=8, p=1, dkLen=32, password='hello', salt='random_salt'。则内存占用 = 128 × N × r = 128 × 16384 × 8 = 16 MB。算法先对 password 和 salt 做 PBKDF2 初始化,然后执行 p=1 路 Salsa20/8 核心(每路 N=16384 次迭代),最终输出 32 字节密钥。实际计算需约 0.1 秒(现代 CPU),内存 16 MB。

适用范围

适用于需要抵抗 ASIC/GPU 暴力破解的密码哈希场景(如加密钱包、SSH 密钥)。不适用于实时认证(延迟 > 100ms)或嵌入式设备(内存 < 1MB)。基于 Percival 2009 年论文(Stronger Key Derivation via Sequential Memory-Hard Functions)。

原理图

Scrypt 密码哈希流程用户密码明文输入Scrypt 算法内存硬 + CPU 密集(N/r/p 参数)哈希值固定长度输出盐值 (Salt)抗 ASIC 设计服务端存储
用户输入 后端处理 输出结果

开发者集成

3 种主流语言 · 复制即用

import hashlib

# 使用 hashlib 内置 scrypt(Python 3.6+)
password = b"my_secure_password"
salt = b"random_salt_123"

# N=2^14, r=8, p=1 是常见参数,dklen=32 输出 256 位密钥
key = hashlib.scrypt(
    password=password,
    salt=salt,
    n=2**14,
    r=8,
    p=1,
    dklen=32
)

print(key.hex())  # 输出 64 字符十六进制字符串
package main

import (
	"crypto/scrypt"
	"encoding/hex"
	"fmt"
	"log"
)

func main() {
	password := []byte("my_secure_password")
	salt := []byte("random_salt_123")

	// N=2^14, r=8, p=1, keyLen=32
	key, err := scrypt.Key(password, salt, 1<<14, 8, 1, 32)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(hex.EncodeToString(key))
}
const { scryptSync, randomBytes } = require('crypto');

const password = 'my_secure_password';
const salt = 'random_salt_123';

// Node.js 内置 scryptSync,N=2^14, r=8, p=1, keylen=32
const key = scryptSync(password, salt, 32, {
  N: 16384,
  r: 8,
  p: 1
});

console.log(key.toString('hex'));

常见问题

8 个高频疑问

Scrypt 在线工具怎么用?直接输入密码就行吗?
是的,在输入框里输入要哈希的密码或文本,点击「计算」按钮即可生成 Scrypt 哈希值。部分工具还提供盐值(salt)、CPU 开销参数(N)、内存开销参数(r)和并行度参数(p)的自定义选项。如果不填盐值,工具会随机生成一个;建议每次哈希使用不同的随机盐值,以抵抗彩虹表攻击。生成的哈希通常以十六进制字符串或 Base64 编码输出,可直接用于密码存储或验证。
Scrypt 和 bcrypt、argon2 有什么区别?哪个更安全?
Scrypt 是内存硬(memory-hard)哈希函数,设计上强制占用大量内存(通过参数 r 控制),使得 ASIC/GPU 并行攻击成本极高。bcrypt 主要消耗 CPU 时间,内存占用小,容易被专用硬件加速。Argon2 是 2015 年密码哈希竞赛胜出者,比 Scrypt 更灵活(有 Argon2d/Argon2i/Argon2id 三种变体),安全性上 Argon2id 通常被认为最优。三者在防暴力破解上都远强于 SHA-256/MD5;选择时优先 Argon2,其次 Scrypt,最后 bcrypt。
我输入的密码很长,Scrypt 计算时间特别久,正常吗?
正常。Scrypt 的计算时间主要由参数 N(CPU/内存迭代次数)决定——N 每翻一倍,耗时和内存占用都翻倍。默认 N=16384(2^14)时,普通密码(如 8 位)在主流 PC 上约需 0.1~0.5 秒;如果密码长达 100 位,哈希过程本身不显著变慢(Scrypt 先对输入做一次 PBKDF2,固定开销),但若同时设置了高 r(如 8)或高 p(如 4),内存占用可达几十 MB,计算时间可能增加到几秒。如果觉得太慢,可以降低 N 值(最低建议 1024),但安全性会下降。
为什么我用 Scrypt 在线工具生成的哈希,跟本地 Python 程序算出来的不一样?
最常见的原因是参数不匹配:盐值不同、N/r/p 参数不同、输出编码不同(十六进制 vs Base64 vs 原始字节)。Scrypt 标准(RFC 7914)定义了参数组合,但各语言库实现时默认值可能不同——例如 Go 的 scrypt.Key() 默认 N=32768、r=8、p=1,而 Python hashlib.scrypt 默认 N=16384、r=8、p=1。另外注意:盐值必须完全一致(包括大小写和编码),否则哈希值必然不同。建议在工具和程序中都明确指定所有参数,并统一输出格式。
Scrypt 在线工具会保存我的密码吗?安全吗?
本工具为后端(BE)实现,密码通过 HTTPS 加密传输到服务器计算,计算完成后服务器不会存储明文密码或哈希结果。可以在浏览器开 DevTools 看 Network 面板:请求体中的密码字段仅用于单次计算,服务器响应后即丢弃。但为绝对安全,建议敏感密码仅在本地计算(纯浏览器 WASM 实现的 Scrypt 工具),避免任何网络传输。如果对该后端实现不放心,可以先用假密码测试,确认无异常后再用真实密码。
Scrypt 的盐值(salt)是什么?必须填吗?
盐值是附加到密码上的随机数据,用于确保相同密码产生不同哈希值,防止彩虹表攻击。Scrypt 标准要求盐值长度至少 16 字节(128 位),推荐 32 字节。在线工具通常会自动生成随机盐值;如果手动填写,建议使用密码学安全的随机数生成器(如 /dev/urandom 输出),不要用用户名、生日等可预测字符串。盐值不需要保密——通常与哈希结果一起存储(如 $salt$hash 格式),验证时提取盐值重新计算比对。
Scrypt 能用于加密文件吗?还是只适合存密码?
Scrypt 是密钥派生函数(KDF),不是加密算法。它的典型用途是将密码(低熵)转化为固定长度的密钥(高熵),再用这个密钥去加密文件(配合 AES/ChaCha20 等对称加密)。很多加密工具(如 VeraCrypt、LUKS)内部就用 Scrypt 作为密码哈希环节。单独用 Scrypt 哈希不能加密文件——它只输出哈希值,无法还原出原始密码,也无法直接解密。如果只是想验证文件完整性,应使用 SHA-256 等通用哈希。
Scrypt 参数 N、r、p 怎么设置才安全又不卡?
推荐最小安全参数:N=16384(2^14)、r=8、p=1,此配置在 2024 年消费级 CPU 上约需 0.2-0.5 秒,内存约 4 MB。如果设备性能较好(如服务器),可提升到 N=32768(2^15)耗时约 1 秒,内存 8 MB。参数应随硬件进步逐年调高:r 控制内存访问粒度(通常 8 即可),p 控制并行度(1 就够了,除非多核优化)。注意:N 必须是 2 的幂,且 N * r * p 不应超过 2^30(约 1 GB),否则会导致内存溢出。
选择 打开 +新窗口 esc关闭