commit a3fd5ec812ee31a7ec4a07053518b64aaa0c3932
parent 6365cdae2b19c6f5aa6ccdd5b6223ae5523314ac
Author: Drew DeVault <sir@cmpwn.com>
Date: Sat, 1 Jun 2024 11:55:56 +0200
crypto::bcrypt: move to extlib
New home: https://git.sr.ht/~sircmpwn/hare-bcrypt
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
4 files changed, 0 insertions(+), 277 deletions(-)
diff --git a/crypto/bcrypt/+test.ha b/crypto/bcrypt/+test.ha
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MPL-2.0
-// (c) Hare authors <https://harelang.org>
-
-use fmt;
-use strings;
-
-@test fn bcrypt() void = {
- const pass = strings::toutf8("hare is cool");
- const hash = generate(pass, DEFAULT_COST);
- defer free(hash);
- assert(compare(hash, pass)!);
- const notpass = strings::toutf8("hare is lame");
- assert(!compare(hash, notpass)!);
-};
-
-@test fn hash() void = {
- const pass = strings::toutf8("allmine");
- const salt = strings::toutf8("XajjQvNhvvRt5GSeFk1xFe");
- const expect = "$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga";
-
- const hash = strings::fromutf8(bcrypt(pass, salt, 10)!)!;
- defer free(hash);
- assert(strings::hassuffix(expect, hash));
-};
diff --git a/crypto/bcrypt/README b/crypto/bcrypt/README
@@ -1,10 +0,0 @@
-This module implements the bcrypt password hash based on Bruce Schneier's
-blowfish. This is a legacy algorithm which is not recommended for new users.
-
-This is a low-level module which implements cryptographic primitives. Direct use
-of cryptographic primitives is not recommended for non-experts, as incorrect use
-of these primitives can easily lead to the introduction of security
-vulnerabilities. Non-experts are advised to use the high-level operations
-available in the top-level [[crypto::]] module.
-
-Be advised that Hare's cryptography implementations have not been audited.
diff --git a/crypto/bcrypt/base64.ha b/crypto/bcrypt/base64.ha
@@ -1,38 +0,0 @@
-// SPDX-License-Identifier: MPL-2.0
-// (c) Hare authors <https://harelang.org>
-
-// bcrypt uses a crappy variant of base64 with its own special alphabet and no
-// padding. This file glues encoding::base64 to the bcrypt semantics.
-use encoding::base64;
-use errors;
-use io;
-use memio;
-
-const alpha: str = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-const b64encoding: base64::encoding = base64::encoding { ... };
-
-@init fn init() void = {
- base64::encoding_init(&b64encoding, alpha);
-};
-
-// Encodes a slice in the bcrypt base64 style, returning a new slice. The caller
-// must free the return value.
-fn b64_encode(src: []u8) []u8 = {
- let sink = memio::dynamic();
- base64::encode(&sink, &b64encoding, src)!;
- let buf = memio::buffer(&sink);
- let i = len(buf);
- for (i > 0 && buf[i - 1] == '='; i -= 1) void;
- return buf[..i];
-};
-
-// Decodes a slice in the bcrypt base64 style, returning a new slice. The
-// caller must free the return value.
-fn b64_decode(src: []u8) ([]u8 | errors::invalid) = {
- let src = alloc(src...);
- defer free(src);
- for (let neq = 4 - len(src) % 4; neq > 0; neq -= 1) {
- append(src, '=');
- };
- return base64::decodeslice(&b64encoding, src);
-};
diff --git a/crypto/bcrypt/bcrypt.ha b/crypto/bcrypt/bcrypt.ha
@@ -1,205 +0,0 @@
-// SPDX-License-Identifier: MPL-2.0
-// (c) Hare authors <https://harelang.org>
-
-// This implementation is not great, but neither is this algorithm. Mostly
-// ported from Go.
-//
-// TODO: Move me into the extlib (hare-x-crypto?)
-use bytes;
-use crypto;
-use crypto::blowfish;
-use crypto::cipher;
-use crypto::random;
-use errors;
-use fmt;
-use io;
-use memio;
-use strconv;
-use strings;
-
-let magic: []u8 = [
- 0x4f, 0x72, 0x70, 0x68,
- 0x65, 0x61, 0x6e, 0x42,
- 0x65, 0x68, 0x6f, 0x6c,
- 0x64, 0x65, 0x72, 0x53,
- 0x63, 0x72, 0x79, 0x44,
- 0x6f, 0x75, 0x62, 0x74,
-];
-
-type hash = struct {
- hash: []u8,
- salt: []u8,
- cost: uint,
- major: u8,
- minor: u8,
-};
-
-// The minimum cost for a bcrypt hash.
-export def MIN_COST: uint = 4;
-
-// The maximum cost for a bcrypt hash.
-export def MAX_COST: uint = 32;
-
-// The recommended default cost for a bcrypt hash.
-export def DEFAULT_COST: uint = 10;
-
-def MAJOR: u8 = '2';
-def MINOR: u8 = 'a';
-def MAX_SALT_SZ: size = 16;
-def MAX_CRYPTED_HASH_SZ: size = 23;
-def ENCODED_SALT_SZ: size = 22;
-def ENCODED_HASH_SZ: size = 31;
-def MIN_HASH_SZ: size = 59;
-
-// Hashes a password using the bcrypt algorithm. The caller must free the return
-// value.
-export fn generate(password: []u8, cost: uint) []u8 = {
- let salt: [MAX_SALT_SZ]u8 = [0...];
- random::buffer(salt);
- const hash = hash_password(password, salt, cost)!;
- defer finish(&hash);
- return mkhash(&hash);
-};
-
-// Compares a password against a bcrypt hash, returning true if the given
-// password matches the hash, or false otherwise. [[errors::invalid]] is
-// returned if the provided hash is not a valid bcrypt hash.
-export fn compare(hash: []u8, password: []u8) (bool | errors::invalid) = {
- const hash = load(hash)?;
- defer finish(&hash);
- const salt = b64_decode(hash.salt)!;
- defer free(salt);
- const other = hash_password(password, salt, hash.cost)?;
- defer finish(&other);
- assert(hash.major == other.major);
- assert(hash.minor == other.minor); // TODO?
- return crypto::compare(hash.hash, other.hash);
-};
-
-fn mkhash(h: *hash) []u8 = {
- let buf = memio::dynamic();
- fmt::fprintf(&buf, "${}$", h.major: rune)!;
- if (h.minor != 0) {
- fmt::fprintf(&buf, "{}", h.minor: rune)!;
- };
- fmt::fprintf(&buf, "${:.2}$", h.cost)!;
- io::write(&buf, h.salt)!;
- io::write(&buf, h.hash)!;
- return memio::buffer(&buf);
-};
-
-fn hash_password(
- password: []u8,
- salt: []u8,
- cost: uint,
-) (hash | errors::invalid) = {
- assert(cost >= MIN_COST && cost <= MAX_COST, "Invalid bcrypt cost");
- let hash = hash {
- major = MAJOR,
- minor = MINOR,
- cost = cost,
- ...
- };
- hash.salt = b64_encode(salt);
- hash.hash = bcrypt(password, hash.salt, hash.cost)?;
- return hash;
-};
-
-fn load(input: []u8) (hash | errors::invalid) = {
- if (len(input) < MIN_HASH_SZ || input[0] != '$') {
- return errors::invalid;
- };
- let hash = hash { ... };
-
- const tok = bytes::tokenize(input[1..], ['$']);
-
- const major = loadtok(&tok)?;
- hash.major = strings::toutf8(major)[0];
- if (hash.major > MAJOR) {
- return errors::invalid;
- };
-
- const minor = loadtok(&tok)?;
- if (minor != "") {
- hash.minor = strings::toutf8(minor)[0];
- };
-
- const cost = loadtok(&tok)?;
- match (strconv::stou(cost)) {
- case let u: uint =>
- hash.cost = u;
- case =>
- return errors::invalid;
- };
-
- let data = strings::toutf8(loadtok(&tok)?);
- if (!(loadtok(&tok) is errors::invalid)) {
- return errors::invalid;
- };
-
- hash.salt = alloc(data[..ENCODED_SALT_SZ]...);
- hash.hash = alloc(data[ENCODED_SALT_SZ..]...);
- return hash;
-};
-
-fn loadtok(tok: *bytes::tokenizer) (str | errors::invalid) = {
- match (bytes::next_token(tok)) {
- case let b: []u8 =>
- match (strings::fromutf8(b)) {
- case let s: str =>
- return s;
- case =>
- return errors::invalid;
- };
- case done =>
- return errors::invalid;
- };
-};
-
-fn bcrypt(password: []u8, salt: []u8, cost: uint) ([]u8 | errors::invalid) = {
- let state: []u8 = alloc(magic...);
- defer free(state);
-
- let bf = expensive_blowfish(password, salt, cost)?;
- defer free(bf);
- for (let i = 0; i < 24; i += 8) {
- for (let j = 0; j < 64; j += 1) {
- cipher::encrypt(bf, state[i..i+8], state[i..i+8]);
- };
- };
-
- // Bug compat: only encode 32 bytes
- const enc = b64_encode(state[..MAX_CRYPTED_HASH_SZ]);
- return enc;
-};
-
-fn expensive_blowfish(
- key: []u8,
- salt: []u8,
- cost: uint,
-) (*blowfish::state | errors::invalid) = {
- let csalt = b64_decode(salt)?;
- defer free(csalt);
-
- // Bug compat: OpenBSD does this cool thing where it treats the string's
- // trailing NUL as part of the key
- let ckey = alloc(key...);
- append(ckey, 0);
- defer free(ckey);
-
- const bf = alloc(blowfish::new());
- blowfish::init_salt(bf, ckey, csalt);
-
- let rounds: u64 = 1u64 << cost;
- for (let i = 0u64; i < rounds; i += 1) {
- blowfish::init(bf, ckey);
- blowfish::init(bf, csalt);
- };
-
- return bf;
-};
-
-fn finish(hash: *hash) void = {
- free(hash.hash);
- free(hash.salt);
-};