Skip to content

Commit 8652cf3

Browse files
[SEC-18095] Add an optional version parameter in HashPassword wrappers
1 parent 5c67d35 commit 8652cf3

13 files changed

Lines changed: 118 additions & 33 deletions

File tree

Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
package.version = "0.10.0"
2+
package.version = "0.10.1"
33
members = [
44
"cli",
55
"ffi",
@@ -20,7 +20,7 @@ rust-argon2 = { version = "3.0", default-features = false }
2020
[package]
2121
name = "devolutions-crypto"
2222
version.workspace = true
23-
authors = ["Philippe Dugre <pdugre@devolutions.net>", "Mathieu Morrissette <mmorrissette@devolutions.net>"]
23+
authors = ["Mathieu Morrissette <mmorrissette@devolutions.net>", "Sebastien Duquette <sduquette@devolutions.net>"]
2424
edition = "2021"
2525
readme = "README_RUST.md"
2626
license = "MIT OR Apache-2.0"

cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "devolutions-crypto-cli"
33
version.workspace = true
4-
authors = ["Philippe Dugre <pdugre@devolutions.net>"]
4+
authors = ["Devolutions <network@devolutions.net>"]
55
edition = "2018"
66

77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

cli/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{borrow::Borrow, convert::TryFrom};
66
#[derive(Debug, Parser)]
77
#[command(name = "devolutions-crypto-cli")]
88
#[command(version = env!("CARGO_PKG_VERSION"))]
9-
#[command(author = "Philippe Dugre <pdugre@devolutions.net>")]
9+
#[command(author = "Devolutions <network@devolutions.net>")]
1010
struct Cli {
1111
#[command(subcommand)]
1212
command: Commands,

ffi/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ edition = "2021"
77
name = "devolutions_crypto_ffi"
88
crate-type = ["cdylib", "staticlib"]
99

10-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11-
1210
[dependencies]
1311
devolutions-crypto = { path = "../" }
1412
zeroize = "1"

ffi/src/lib.rs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ use zeroize::Zeroizing;
5555

5656
const VERSION: &str = env!("CARGO_PKG_VERSION");
5757

58+
pub const PASSWORD_HASH_LATEST: u16 = 0;
59+
pub const PASSWORD_HASH_V1: u16 = 1;
60+
pub const PASSWORD_HASH_V2: u16 = 1;
61+
5862
/// Encrypt a data blob
5963
/// # Arguments
6064
/// * `data` - Pointer to the data to encrypt.
@@ -744,6 +748,7 @@ pub extern "C" fn SignSize(_version: u16) -> i64 {
744748
/// * `result` - Pointer to the buffer to write the hash to.
745749
/// * `result_length` - Length of the buffer to write the hash to. You can get the value by
746750
/// calling HashPasswordLength() beforehand.
751+
/// * `version` - Version to use. Use PASSWORD_HASH_LATEST for the latest one.
747752
/// # Returns
748753
/// This returns the length of the hash. If there is an error, it will return the
749754
/// appropriate error code defined in DevoCryptoError.
@@ -755,19 +760,25 @@ pub unsafe extern "C" fn HashPassword(
755760
password_length: usize,
756761
result: *mut u8,
757762
result_length: usize,
763+
version: u16,
758764
) -> i64 {
759765
if password.is_null() || result.is_null() {
760766
return Error::NullPointer.error_code();
761767
};
762768

763-
if result_length != HashPasswordLength() as usize {
769+
let version = match PasswordHashVersion::try_from(version) {
770+
Ok(v) => v,
771+
Err(_) => return Error::UnknownVersion.error_code(),
772+
};
773+
774+
if result_length != HashPasswordLength(version as u16) as usize {
764775
return Error::InvalidOutputLength.error_code();
765776
};
766777

767778
let password = slice::from_raw_parts(password, password_length);
768779
let result = slice::from_raw_parts_mut(result, result_length);
769780

770-
let res: Zeroizing<Vec<u8>> = match hash_password(password, PasswordHashVersion::Latest) {
781+
let res: Zeroizing<Vec<u8>> = match hash_password(password, version) {
771782
Ok(x) => Zeroizing::new(x.into()),
772783
Err(e) => return e.error_code(),
773784
};
@@ -778,17 +789,19 @@ pub unsafe extern "C" fn HashPassword(
778789
}
779790

780791
/// Returns the length of the hash to input as `result_length` in `HashPassword()`.
781-
/// The size reflects the default Argon2id parameters.
792+
/// # Arguments
793+
/// * `version` - Version to use. Use 0 for the latest one.
782794
/// # Returns
783-
/// Returns the length of the hash.
795+
/// Returns the length of the hash, or a negative error code for an unknown version.
784796
#[no_mangle]
785-
pub extern "C" fn HashPasswordLength() -> i64 {
786-
// 8 (PasswordHash header)
787-
// + 4 (u32 params_len)
788-
// + 8 (DerivationParameters header)
789-
// + GetDefaultArgon2ParametersSize() (Argon2Parameters default)
790-
// + 32 (Argon2 default output length)
791-
8 + 4 + 8 + GetDefaultArgon2ParametersSize() + 32
797+
pub extern "C" fn HashPasswordLength(version: u16) -> i64 {
798+
match version {
799+
// V1: PBKDF2 — fixed layout: 4 (iterations) + 32 (salt) + 32 (hash) = 68, plus 8-byte header
800+
1 => 8 + 68,
801+
// V2 / Latest: Argon2id — header + params_len field + DerivationParameters + hash
802+
0 | 2 => 8 + 4 + 8 + GetDefaultArgon2ParametersSize() + 32,
803+
_ => Error::UnknownVersion.error_code(),
804+
}
792805
}
793806

794807
/// Hash a password using caller-supplied serialized [`DerivationParameters`].
@@ -2419,8 +2432,8 @@ fn test_hash_password_length() {
24192432
.unwrap()
24202433
.into();
24212434

2422-
assert_eq!(HashPasswordLength() as usize, small_password_hash.len());
2423-
assert_eq!(HashPasswordLength() as usize, long_password_hash.len());
2435+
assert_eq!(HashPasswordLength(PASSWORD_HASH_LATEST) as usize, small_password_hash.len());
2436+
assert_eq!(HashPasswordLength(PASSWORD_HASH_LATEST) as usize, long_password_hash.len());
24242437
}
24252438

24262439
#[test]

python/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
[project]
22
name = "devolutions-crypto"
3-
version = "2026.6.16"
3+
version = "2026.6.22"
44
description = "An abstraction layer for the cryptography used by Devolutions"
55
readme = "PYPI_README.md"
66
authors = [
7-
{ name = "Philippe Dugre", email = "pdugre@devolutions.net" },
8-
{ name = "Mathieu Morrissette", email = "mmorrissette@devolutions.net" }
7+
{ name = "Mathieu Morrissette", email = "mmorrissette@devolutions.net" },
8+
{ name = "Sebastien Duquette", email = "sduquette@devolutions.net" },
99
]
1010
license = { text = "MIT OR Apache-2.0" }
1111
requires-python = ">=3.10"

wrappers/csharp/src/Enums.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ public enum DataType
5454
KdfEncryptedData = 9
5555
}
5656

57+
/// <summary>
58+
/// Devolutions Crypto Password Hash Version.
59+
/// </summary>
60+
public enum PasswordHashVersion
61+
{
62+
/// <summary>
63+
/// This is the latest version. (Currently Argon2id)
64+
/// </summary>
65+
Latest = 0,
66+
67+
/// <summary>
68+
/// PBKDF2-HMAC-SHA256.
69+
/// </summary>
70+
V1 = 1,
71+
72+
/// <summary>
73+
/// Argon2id.
74+
/// </summary>
75+
V2 = 2,
76+
}
77+
5778
/// <summary>
5879
/// Devolutions Crypto Cipher Version.
5980
/// </summary>

wrappers/csharp/src/Managed.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -674,23 +674,24 @@ public static bool VerifyPassword(byte[] password, byte[] hash, ILegacyHasher? l
674674
/// Use <see cref="HashPasswordWithParams"/> to supply custom <see cref="DerivationParameters"/>.
675675
/// </summary>
676676
/// <param name="password">The password to hash in bytes.</param>
677+
/// <param name="version">The version of the password hashing algorithm to use. Defaults to <see cref="PasswordHashVersion.Latest"/>.</param>
677678
/// <returns>Returns the hashed password in bytes.</returns>
678-
public static byte[] HashPassword(byte[] password)
679+
public static byte[] HashPassword(byte[] password, PasswordHashVersion version = PasswordHashVersion.Latest)
679680
{
680681
if (password == null || password.Length == 0)
681682
{
682683
throw new DevolutionsCryptoException(ManagedError.InvalidParameter);
683684
}
684685

685-
long hashLength = Native.HashPasswordLengthNative();
686+
long hashLength = Native.HashPasswordLengthNative((ushort)version);
686687

687688
if (hashLength < 0)
688689
{
689690
Utils.HandleError(hashLength);
690691
}
691692

692693
byte[] result = new byte[hashLength];
693-
long res = Native.HashPasswordNative(password, (UIntPtr)password.Length, result, (UIntPtr)result.Length);
694+
long res = Native.HashPasswordNative(password, (UIntPtr)password.Length, result, (UIntPtr)result.Length, (ushort)version);
694695

695696
if (res < 0)
696697
{

wrappers/csharp/src/Native.Core.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,10 @@ public static partial class Native
119119
internal static extern long GetDefaultArgon2ParametersSizeNative();
120120

121121
[DllImport(LibName, EntryPoint = "HashPasswordLength", CallingConvention = CallingConvention.Cdecl)]
122-
internal static extern long HashPasswordLengthNative();
122+
internal static extern long HashPasswordLengthNative(ushort version);
123123

124124
[DllImport(LibName, EntryPoint = "HashPassword", CallingConvention = CallingConvention.Cdecl)]
125-
internal static extern long HashPasswordNative(byte[] password, UIntPtr passwordLength, byte[] result, UIntPtr resultLength);
125+
internal static extern long HashPasswordNative(byte[] password, UIntPtr passwordLength, byte[] result, UIntPtr resultLength, ushort version);
126126

127127
[DllImport(LibName, EntryPoint = "HashPasswordWithParamsLength", CallingConvention = CallingConvention.Cdecl)]
128128
internal static extern long HashPasswordWithParamsLengthNative(byte[] derivationParams, UIntPtr derivationParamsLength);

0 commit comments

Comments
 (0)