pub fn kdf( allocator: mem.Allocator, derived_key: []u8, password: []const u8, salt: []const u8, params: Params, ) KdfError!voidApply scrypt to generate a key from a password.
scrypt is defined in RFC 7914.
allocator: mem.Allocator.
derived_key: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length.
May be uninitialized. All bytes will be overwritten.
Maximum size is derived_key.len / 32 == 0xffff_ffff.
password: Arbitrary sequence of bytes of any length.
salt: Arbitrary sequence of bytes of any length.
params: Params.
pub fn kdf(
allocator: mem.Allocator,
derived_key: []u8,
password: []const u8,
salt: []const u8,
params: Params,
) KdfError!void {
if (derived_key.len == 0) return KdfError.WeakParameters;
if (derived_key.len / 32 > 0xffff_ffff) return KdfError.OutputTooLong;
if (params.ln == 0 or params.r == 0 or params.p == 0) return KdfError.WeakParameters;
const n64 = @as(u64, 1) << params.ln;
if (n64 > max_size) return KdfError.WeakParameters;
const n = @as(usize, @intCast(n64));
if (@as(u64, params.r) * @as(u64, params.p) >= 1 << 30 or
params.r > max_int / 128 / @as(u64, params.p) or
params.r > max_int / 256 or
n > max_int / 128 / @as(u64, params.r)) return KdfError.WeakParameters;
const xy = try allocator.alignedAlloc(u32, 16, 64 * params.r);
defer allocator.free(xy);
const v = try allocator.alignedAlloc(u32, 16, 32 * n * params.r);
defer allocator.free(v);
var dk = try allocator.alignedAlloc(u8, 16, params.p * 128 * params.r);
defer allocator.free(dk);
try pwhash.pbkdf2(dk, password, salt, 1, HmacSha256);
var i: u32 = 0;
while (i < params.p) : (i += 1) {
smix(@alignCast(dk[i * 128 * params.r ..]), params.r, n, v, xy);
}
try pwhash.pbkdf2(derived_key, password, dk, 1, HmacSha256);
}pub fn strHash( password: []const u8, options: HashOptions, out: []u8, ) Error![]const u8Compute a hash of a password using the scrypt key derivation function. The function returns a string that includes all the parameters required for verification.
pub fn strHash(
password: []const u8,
options: HashOptions,
out: []u8,
) Error![]const u8 {
const allocator = options.allocator orelse return Error.AllocatorRequired;
switch (options.encoding) {
.phc => return PhcFormatHasher.create(allocator, password, options.params, out),
.crypt => return CryptFormatHasher.create(allocator, password, options.params, out),
}
}pub fn strVerify( str: []const u8, password: []const u8, options: VerifyOptions, ) Error!voidVerify that a previously computed hash is valid for a given password.
pub fn strVerify(
str: []const u8,
password: []const u8,
options: VerifyOptions,
) Error!void {
const allocator = options.allocator orelse return Error.AllocatorRequired;
if (mem.startsWith(u8, str, crypt_format.prefix)) {
return CryptFormatHasher.verify(allocator, str, password);
} else {
return PhcFormatHasher.verify(allocator, str, password);
}
}const std = @import("std");
const crypto = std.crypto;
const fmt = std.fmt;
const io = std.io;
const math = std.math;
const mem = std.mem;
const meta = std.meta;
const pwhash = crypto.pwhash;
const phc_format = @import("phc_encoding.zig");
const HmacSha256 = crypto.auth.hmac.sha2.HmacSha256;
const KdfError = pwhash.KdfError;
const HasherError = pwhash.HasherError;
const EncodingError = phc_format.Error;
const Error = pwhash.Error;
const max_size = math.maxInt(usize);
const max_int = max_size >> 1;
const default_salt_len = 32;
const default_hash_len = 32;
const max_salt_len = 64;
const max_hash_len = 64;
fn blockCopy(dst: []align(16) u32, src: []align(16) const u32, n: usize) void {
@memcpy(dst[0 .. n * 16], src[0 .. n * 16]);
}
fn blockXor(dst: []align(16) u32, src: []align(16) const u32, n: usize) void {
for (src[0 .. n * 16], 0..) |v, i| {
dst[i] ^= v;
}
}
const QuarterRound = struct { a: usize, b: usize, c: usize, d: u6 };
fn Rp(a: usize, b: usize, c: usize, d: u6) QuarterRound {
return QuarterRound{ .a = a, .b = b, .c = c, .d = d };
}
fn salsa8core(b: *align(16) [16]u32) void {
const arx_steps = comptime [_]QuarterRound{
Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18),
Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18),
Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18),
Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18),
Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18),
Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18),
Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18),
Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18),
};
var x = b.*;
var j: usize = 0;
while (j < 8) : (j += 2) {
inline for (arx_steps) |r| {
x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d);
}
}
j = 0;
while (j < 16) : (j += 1) {
b[j] +%= x[j];
}
}
fn salsaXor(tmp: *align(16) [16]u32, in: []align(16) const u32, out: []align(16) u32) void {
blockXor(tmp, in, 1);
salsa8core(tmp);
blockCopy(out, tmp, 1);
}
fn blockMix(tmp: *align(16) [16]u32, in: []align(16) const u32, out: []align(16) u32, r: u30) void {
blockCopy(tmp, @alignCast(in[(2 * r - 1) * 16 ..]), 1);
var i: usize = 0;
while (i < 2 * r) : (i += 2) {
salsaXor(tmp, @alignCast(in[i * 16 ..]), @alignCast(out[i * 8 ..]));
salsaXor(tmp, @alignCast(in[i * 16 + 16 ..]), @alignCast(out[i * 8 + r * 16 ..]));
}
}
fn integerify(b: []align(16) const u32, r: u30) u64 {
const j = (2 * r - 1) * 16;
return @as(u64, b[j]) | @as(u64, b[j + 1]) << 32;
}
fn smix(b: []align(16) u8, r: u30, n: usize, v: []align(16) u32, xy: []align(16) u32) void {
const x: []align(16) u32 = @alignCast(xy[0 .. 32 * r]);
const y: []align(16) u32 = @alignCast(xy[32 * r ..]);
for (x, 0..) |*v1, j| {
v1.* = mem.readInt(u32, b[4 * j ..][0..4], .little);
}
var tmp: [16]u32 align(16) = undefined;
var i: usize = 0;
while (i < n) : (i += 2) {
blockCopy(@alignCast(v[i * (32 * r) ..]), x, 2 * r);
blockMix(&tmp, x, y, r);
blockCopy(@alignCast(v[(i + 1) * (32 * r) ..]), y, 2 * r);
blockMix(&tmp, y, x, r);
}
i = 0;
while (i < n) : (i += 2) {
var j = @as(usize, @intCast(integerify(x, r) & (n - 1)));
blockXor(x, @alignCast(v[j * (32 * r) ..]), 2 * r);
blockMix(&tmp, x, y, r);
j = @as(usize, @intCast(integerify(y, r) & (n - 1)));
blockXor(y, @alignCast(v[j * (32 * r) ..]), 2 * r);
blockMix(&tmp, y, x, r);
}
for (x, 0..) |v1, j| {
mem.writeInt(u32, b[4 * j ..][0..4], v1, .little);
}
}
/// Scrypt parameters
pub const Params = struct {
const Self = @This();
/// The CPU/Memory cost parameter [ln] is log2(N).
ln: u6,
/// The [r]esource usage parameter specifies the block size.
r: u30,
/// The [p]arallelization parameter.
/// A large value of [p] can be used to increase the computational cost of scrypt without
/// increasing the memory usage.
p: u30,
/// Baseline parameters for interactive logins
pub const interactive = Self.fromLimits(524288, 16777216);
/// Baseline parameters for offline usage
pub const sensitive = Self.fromLimits(33554432, 1073741824);
/// Recommended parameters according to the
/// [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
pub const owasp = Self{ .ln = 17, .r = 8, .p = 1 };
/// Create parameters from ops and mem limits, where mem_limit given in bytes
pub fn fromLimits(ops_limit: u64, mem_limit: usize) Self {
const ops = @max(32768, ops_limit);
const r: u30 = 8;
if (ops < mem_limit / 32) {
const max_n = ops / (r * 4);
return Self{ .r = r, .p = 1, .ln = @as(u6, @intCast(math.log2(max_n))) };
} else {
const max_n = mem_limit / (@as(usize, @intCast(r)) * 128);
const ln = @as(u6, @intCast(math.log2(max_n)));
const max_rp = @min(0x3fffffff, (ops / 4) / (@as(u64, 1) << ln));
return Self{ .r = r, .p = @as(u30, @intCast(max_rp / @as(u64, r))), .ln = ln };
}
}
};
/// Apply scrypt to generate a key from a password.
///
/// scrypt is defined in RFC 7914.
///
/// allocator: mem.Allocator.
///
/// derived_key: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length.
/// May be uninitialized. All bytes will be overwritten.
/// Maximum size is `derived_key.len / 32 == 0xffff_ffff`.
///
/// password: Arbitrary sequence of bytes of any length.
///
/// salt: Arbitrary sequence of bytes of any length.
///
/// params: Params.
pub fn kdf(
allocator: mem.Allocator,
derived_key: []u8,
password: []const u8,
salt: []const u8,
params: Params,
) KdfError!void {
if (derived_key.len == 0) return KdfError.WeakParameters;
if (derived_key.len / 32 > 0xffff_ffff) return KdfError.OutputTooLong;
if (params.ln == 0 or params.r == 0 or params.p == 0) return KdfError.WeakParameters;
const n64 = @as(u64, 1) << params.ln;
if (n64 > max_size) return KdfError.WeakParameters;
const n = @as(usize, @intCast(n64));
if (@as(u64, params.r) * @as(u64, params.p) >= 1 << 30 or
params.r > max_int / 128 / @as(u64, params.p) or
params.r > max_int / 256 or
n > max_int / 128 / @as(u64, params.r)) return KdfError.WeakParameters;
const xy = try allocator.alignedAlloc(u32, 16, 64 * params.r);
defer allocator.free(xy);
const v = try allocator.alignedAlloc(u32, 16, 32 * n * params.r);
defer allocator.free(v);
var dk = try allocator.alignedAlloc(u8, 16, params.p * 128 * params.r);
defer allocator.free(dk);
try pwhash.pbkdf2(dk, password, salt, 1, HmacSha256);
var i: u32 = 0;
while (i < params.p) : (i += 1) {
smix(@alignCast(dk[i * 128 * params.r ..]), params.r, n, v, xy);
}
try pwhash.pbkdf2(derived_key, password, dk, 1, HmacSha256);
}
const crypt_format = struct {
/// String prefix for scrypt
pub const prefix = "$7{{CONTENT}}quot;;
/// Standard type for a set of scrypt parameters, with the salt and hash.
pub fn HashResult(comptime crypt_max_hash_len: usize) type {
return struct {
ln: u6,
r: u30,
p: u30,
salt: []const u8,
hash: BinValue(crypt_max_hash_len),
};
}
const Codec = CustomB64Codec("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".*);
/// A wrapped binary value whose maximum size is `max_len`.
///
/// This type must be used whenever a binary value is encoded in a PHC-formatted string.
/// This includes `salt`, `hash`, and any other binary parameters such as keys.
///
/// Once initialized, the actual value can be read with the `constSlice()` function.
pub fn BinValue(comptime max_len: usize) type {
return struct {
const Self = @This();
const capacity = max_len;
const max_encoded_length = Codec.encodedLen(max_len);
buf: [max_len]u8 = undefined,
len: usize = 0,
/// Wrap an existing byte slice
pub fn fromSlice(slice: []const u8) EncodingError!Self {
if (slice.len > capacity) return EncodingError.NoSpaceLeft;
var bin_value: Self = undefined;
@memcpy(bin_value.buf[0..slice.len], slice);
bin_value.len = slice.len;
return bin_value;
}
/// Return the slice containing the actual value.
pub fn constSlice(self: *const Self) []const u8 {
return self.buf[0..self.len];
}
fn fromB64(self: *Self, str: []const u8) !void {
const len = Codec.decodedLen(str.len);
if (len > self.buf.len) return EncodingError.NoSpaceLeft;
try Codec.decode(self.buf[0..len], str);
self.len = len;
}
fn toB64(self: *const Self, buf: []u8) ![]const u8 {
const value = self.constSlice();
const len = Codec.encodedLen(value.len);
if (len > buf.len) return EncodingError.NoSpaceLeft;
const encoded = buf[0..len];
Codec.encode(encoded, value);
return encoded;
}
};
}
/// Expand binary data into a salt for the modular crypt format.
pub fn saltFromBin(comptime len: usize, salt: [len]u8) [Codec.encodedLen(len)]u8 {
var buf: [Codec.encodedLen(len)]u8 = undefined;
Codec.encode(&buf, &salt);
return buf;
}
/// Deserialize a string into a structure `T` (matching `HashResult`).
pub fn deserialize(comptime T: type, str: []const u8) EncodingError!T {
var out: T = undefined;
if (str.len < 16) return EncodingError.InvalidEncoding;
if (!mem.eql(u8, prefix, str[0..3])) return EncodingError.InvalidEncoding;
out.ln = try Codec.intDecode(u6, str[3..4]);
out.r = try Codec.intDecode(u30, str[4..9]);
out.p = try Codec.intDecode(u30, str[9..14]);
var it = mem.splitScalar(u8, str[14..], '