commit 23acdbd445d2a9a8f22eecc2459cc4e962d4a4f1
parent 243a7188d9d67e55ef18216b702a770873287155
Author: Antoine A <>
Date: Fri, 4 Sep 2026 12:03:27 +0200
bank: fix bcrypt compatibility with kotlin
Diffstat:
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/libeufin-bank/src/pw.rs b/libeufin-bank/src/pw.rs
@@ -61,6 +61,18 @@ pub enum PwCrypto {
Sha256,
}
+fn bcrypt(cost: u32, salt: [u8; 16], pw: &[u8]) -> [u8; 24] {
+ // The bcrypt spec specifies that passwords should be null terminated
+ // strings, but if longer than 72 bytes, are truncated at 72 bytes (thereby
+ // losing the null byte at the end).
+ let copy_len = pw.len().min(72);
+ let mut pass = [0u8; 72];
+ pass[..copy_len].copy_from_slice(&pw[..copy_len]);
+ let used = (copy_len + 1).min(72);
+ let truncated = &pass[..used];
+ bcrypt::bcrypt(cost, salt, truncated)
+}
+
impl PwCrypto {
/** Hash [pw] using [cfg] hashing method */
pub fn hashpw(&self, pw: &str) -> String {
@@ -68,7 +80,7 @@ impl PwCrypto {
PwCrypto::Bcrypt { cost } => {
let mut salt = [0u8; 16];
SysRng.try_fill_bytes(&mut salt).unwrap();
- let pwh = bcrypt::hash_with_salt_bytes(pw.as_bytes(), *cost, salt).unwrap();
+ let pwh = bcrypt(*cost, salt, pw.as_bytes());
format!("bcrypt${cost}${}${}", base64::fmt(salt), base64::fmt(pwh))
}
PwCrypto::Sha256 => {
@@ -103,7 +115,7 @@ impl PwCrypto {
let salt = base64::decode(salt)?
.try_into()
.map_err(|_| anyhow!("bad password hash format"))?;
- let pwh = bcrypt::hash_with_salt_bytes(pw, cost, salt)?;
+ let pwh = bcrypt(cost, salt, pw.as_bytes());
let pwh = base64::encode(pwh);
(
pwh == hash,
@@ -190,4 +202,14 @@ fn pwh() {
outdated: true
}
);
+
+ // Check compatibility with kotlin hashes
+ let hash = "bcrypt$4$PfNp4JBeMU/t5mS5zaUY3A==$0Tjw0KzNA3ca3y9BVkh/e3TJDlHsdkEA";
+ assert_eq!(
+ crypto.checkpw("password", &hash).unwrap(),
+ PwCheck {
+ matches: true,
+ outdated: false
+ }
+ );
}