From 6240e8a42e6f1d56852242aac7729badf3b578df Mon Sep 17 00:00:00 2001 From: Randil Date: Mon, 17 Mar 2025 22:04:55 +0530 Subject: [PATCH 01/26] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/Dependencies.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 6c7f6f3d..6ca89f60 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "crypto" -version = "2.9.0" +version = "2.9.1" authors = ["Ballerina"] keywords = ["security", "hash", "hmac", "sign", "encrypt", "decrypt", "private key", "public key"] repository = "https://github.com/ballerina-platform/module-ballerina-crypto" @@ -15,8 +15,8 @@ graalvmCompatible = true [[platform.java21.dependency]] groupId = "io.ballerina.stdlib" artifactId = "crypto-native" -version = "2.9.0" -path = "../native/build/libs/crypto-native-2.9.0.jar" +version = "2.9.1" +path = "../native/build/libs/crypto-native-2.9.1-SNAPSHOT.jar" [[platform.java21.dependency]] groupId = "org.bouncycastle" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index d79d394d..6488f734 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.12.0" [[package]] org = "ballerina" name = "crypto" -version = "2.9.0" +version = "2.9.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, From f11b98a4c7815bc074588bcb6be54c67f95100ed Mon Sep 17 00:00:00 2001 From: Randil Date: Mon, 17 Mar 2025 22:07:15 +0530 Subject: [PATCH 02/26] Move password hashing methods to Password class removing PasswordArgon2 class --- ballerina/hash.bal | 4 +- .../stdlib/crypto/nativeimpl/Password.java | 152 ++++++++++++++- .../crypto/nativeimpl/PasswordArgon2.java | 175 ------------------ 3 files changed, 150 insertions(+), 181 deletions(-) delete mode 100644 native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/PasswordArgon2.java diff --git a/ballerina/hash.bal b/ballerina/hash.bal index 3fb4671a..762dd222 100644 --- a/ballerina/hash.bal +++ b/ballerina/hash.bal @@ -162,7 +162,7 @@ public isolated function verifyBcrypt(string password, string hashedPassword) re # + return - Argon2id hashed password string or Error if hashing fails public isolated function hashArgon2(string password, int iterations = 3, int memory = 65536, int parallelism = 4) returns string|Error = @java:Method { name: "hashPasswordArgon2", - 'class: "io.ballerina.stdlib.crypto.nativeimpl.PasswordArgon2" + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" } external; # Verifies if a password matches an Argon2id hashed password. @@ -177,5 +177,5 @@ public isolated function hashArgon2(string password, int iterations = 3, int mem # + return - Boolean indicating if password matches or Error if verification fails public isolated function verifyArgon2(string password, string hashedPassword) returns boolean|Error = @java:Method { name: "verifyPasswordArgon2", - 'class: "io.ballerina.stdlib.crypto.nativeimpl.PasswordArgon2" + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" } external; diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java index 4094cc7a..89465588 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java @@ -21,18 +21,24 @@ import io.ballerina.runtime.api.values.BString; import io.ballerina.stdlib.crypto.CryptoUtils; import io.ballerina.stdlib.crypto.PasswordUtils; +import org.bouncycastle.crypto.generators.Argon2BytesGenerator; import org.bouncycastle.crypto.generators.BCrypt; +import org.bouncycastle.crypto.params.Argon2Parameters; +import org.bouncycastle.util.encoders.Base64; + +import java.nio.charset.StandardCharsets; /** - * Native implementation of BCrypt password hashing functions. - * Provides methods for hashing passwords, verifying hashes, and generating salts using the BCrypt algorithm. - * - * @since ... + * Native implementation of password hashing functions. + * Provides methods for hashing passwords, verifying hashes, and generating salts using + * the BCrypt and Argon2id algorithms. */ public class Password { private Password() {} + // BCrypt methods + /** * Hash a password using BCrypt with a custom work factor. * @@ -135,4 +141,142 @@ public static Object generateSalt(long workFactor) { public static Object generateSalt() { return generateSalt(PasswordUtils.DEFAULT_WORK_FACTOR); } + + // Argon2 methods + + /** + * Hash a password using Argon2 with default parameters. + * + * @param password the password to hash + * @return hashed password string or error + */ + public static Object hashPasswordArgon2(BString password) { + return hashPasswordArgon2(password, PasswordUtils.DEFAULT_ITERATIONS, PasswordUtils.DEFAULT_MEMORY, + PasswordUtils.DEFAULT_PARALLELISM); + } + + /** + * Hash a password using Argon2 with custom parameters. + * + * @param password the password to hash + * @param iterations number of iterations + * @param memory memory usage in KB + * @param parallelism number of parallel threads + * @return hashed password string or error + */ + public static Object hashPasswordArgon2(BString password, long iterations, long memory, long parallelism) { + try { + if (iterations <= 0) { + return CryptoUtils.createError("Iterations must be positive"); + } + if (memory < 8192) { + return CryptoUtils.createError("Memory must be at least 8192 KB (8MB)"); + } + if (parallelism <= 0) { + return CryptoUtils.createError("Parallelism must be positive"); + } + if (password.getValue().length() == 0) { + return CryptoUtils.createError("Password cannot be empty"); + } + + byte[] salt = PasswordUtils.generateRandomSalt(); + + Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id) + .withSalt(salt) + .withIterations((int) iterations) + .withMemoryAsKB((int) memory) + .withParallelism((int) parallelism) + .build(); + + byte[] hash = new byte[PasswordUtils.HASH_LENGTH]; + Argon2BytesGenerator generator = new Argon2BytesGenerator(); + generator.init(params); + generator.generateBytes(password.getValue().getBytes(StandardCharsets.UTF_8), hash); + + String saltBase64 = Base64.toBase64String(salt); + String hashBase64 = Base64.toBase64String(hash); + + String result = PasswordUtils.formatArgon2Hash(memory, iterations, parallelism, saltBase64, hashBase64); + + return StringUtils.fromString(result); + } catch (Exception e) { + return CryptoUtils.createError("Error occurred while hashing password with Argon2: " + e.getMessage()); + } + } + + /** + * Verify a password against an Argon2 hash. + * + * @param password the password to verify + * @param hashedPassword the hashed password to verify against + * @return true if password matches, false if not, or error if verification fails + */ + public static Object verifyPasswordArgon2(BString password, BString hashedPassword) { + try { + String hash = hashedPassword.getValue(); + if (!hash.startsWith("$argon2id$")) { + return CryptoUtils.createError("Invalid Argon2 hash format"); + } + + String[] parts = hash.split("\\$"); + if (parts.length != 6) { + return CryptoUtils.createError("Invalid Argon2 hash format"); + } + + String[] params = parts[3].split(","); + int memory = Integer.parseInt(params[0].substring(2)); + int iterations = Integer.parseInt(params[1].substring(2)); + int parallelism = Integer.parseInt(params[2].substring(2)); + + byte[] salt = Base64.decode(parts[4]); + byte[] originalHash = Base64.decode(parts[5]); + + Argon2Parameters parameters = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id) + .withSalt(salt) + .withIterations(iterations) + .withMemoryAsKB(memory) + .withParallelism(parallelism) + .build(); + + byte[] newHash = new byte[PasswordUtils.HASH_LENGTH]; + Argon2BytesGenerator generator = new Argon2BytesGenerator(); + generator.init(parameters); + generator.generateBytes(password.getValue().getBytes(StandardCharsets.UTF_8), newHash); + + return PasswordUtils.constantTimeArrayEquals(newHash, originalHash); + } catch (Exception e) { + return CryptoUtils.createError("Error occurred while verifying password: " + e.getMessage()); + } + } + + /** + * Generate a salt string for Argon2 with default parameters. + * + * @return formatted salt string or error + */ + public static Object generateSaltArgon2() { + return generateSaltArgon2(PasswordUtils.DEFAULT_ITERATIONS, PasswordUtils.DEFAULT_MEMORY, + PasswordUtils.DEFAULT_PARALLELISM); + } + + /** + * Generate a salt string for Argon2 with custom parameters. + * + * @param iterations number of iterations + * @param memory memory usage in KB + * @param parallelism number of parallel threads + * @return formatted salt string or error + */ + public static Object generateSaltArgon2(long iterations, long memory, long parallelism) { + try { + byte[] salt = PasswordUtils.generateRandomSalt(); + String saltBase64 = Base64.toBase64String(salt); + + String result = PasswordUtils.formatArgon2Salt(memory, iterations, parallelism, saltBase64); + + return StringUtils.fromString(result); + } catch (Exception e) { + return CryptoUtils.createError("Error occurred while generating Argon2 salt: " + e.getMessage()); + } + } } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/PasswordArgon2.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/PasswordArgon2.java deleted file mode 100644 index d46479e6..00000000 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/PasswordArgon2.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package io.ballerina.stdlib.crypto.nativeimpl; - -import io.ballerina.runtime.api.utils.StringUtils; -import io.ballerina.runtime.api.values.BString; -import io.ballerina.stdlib.crypto.CryptoUtils; -import io.ballerina.stdlib.crypto.PasswordUtils; -import org.bouncycastle.crypto.generators.Argon2BytesGenerator; -import org.bouncycastle.crypto.params.Argon2Parameters; -import org.bouncycastle.util.encoders.Base64; - -import java.nio.charset.StandardCharsets; - -/** - * Native implementation of Argon2 password hashing functions. - * Provides methods for hashing passwords, verifying hashes, and generating salts using the Argon2id algorithm. - * - * @since ... - */ -public class PasswordArgon2 { - - private PasswordArgon2() {} - - /** - * Hash a password using Argon2 with default parameters. - * - * @param password the password to hash - * @return hashed password string or error - */ - public static Object hashPasswordArgon2(BString password) { - return hashPasswordArgon2(password, PasswordUtils.DEFAULT_ITERATIONS, PasswordUtils.DEFAULT_MEMORY, - PasswordUtils.DEFAULT_PARALLELISM); - } - - /** - * Hash a password using Argon2 with custom parameters. - * - * @param password the password to hash - * @param iterations number of iterations - * @param memory memory usage in KB - * @param parallelism number of parallel threads - * @return hashed password string or error - */ - public static Object hashPasswordArgon2(BString password, long iterations, long memory, long parallelism) { - try { - if (iterations <= 0) { - return CryptoUtils.createError("Iterations must be positive"); - } - if (memory < 8192) { - return CryptoUtils.createError("Memory must be at least 8192 KB (8MB)"); - } - if (parallelism <= 0) { - return CryptoUtils.createError("Parallelism must be positive"); - } - if (password.getValue().length() == 0) { - return CryptoUtils.createError("Password cannot be empty"); - } - - byte[] salt = PasswordUtils.generateRandomSalt(); - - Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id) - .withSalt(salt) - .withIterations((int) iterations) - .withMemoryAsKB((int) memory) - .withParallelism((int) parallelism) - .build(); - - byte[] hash = new byte[PasswordUtils.HASH_LENGTH]; - Argon2BytesGenerator generator = new Argon2BytesGenerator(); - generator.init(params); - generator.generateBytes(password.getValue().getBytes(StandardCharsets.UTF_8), hash); - - String saltBase64 = Base64.toBase64String(salt); - String hashBase64 = Base64.toBase64String(hash); - - String result = PasswordUtils.formatArgon2Hash(memory, iterations, parallelism, saltBase64, hashBase64); - - return StringUtils.fromString(result); - } catch (Exception e) { - return CryptoUtils.createError("Error occurred while hashing password with Argon2: " + e.getMessage()); - } - } - - /** - * Verify a password against an Argon2 hash. - * - * @param password the password to verify - * @param hashedPassword the hashed password to verify against - * @return true if password matches, false if not, or error if verification fails - */ - public static Object verifyPasswordArgon2(BString password, BString hashedPassword) { - try { - String hash = hashedPassword.getValue(); - if (!hash.startsWith("$argon2id$")) { - return CryptoUtils.createError("Invalid Argon2 hash format"); - } - - String[] parts = hash.split("\\$"); - if (parts.length != 6) { - return CryptoUtils.createError("Invalid Argon2 hash format"); - } - - String[] params = parts[3].split(","); - int memory = Integer.parseInt(params[0].substring(2)); - int iterations = Integer.parseInt(params[1].substring(2)); - int parallelism = Integer.parseInt(params[2].substring(2)); - - byte[] salt = Base64.decode(parts[4]); - byte[] originalHash = Base64.decode(parts[5]); - - Argon2Parameters parameters = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id) - .withSalt(salt) - .withIterations(iterations) - .withMemoryAsKB(memory) - .withParallelism(parallelism) - .build(); - - byte[] newHash = new byte[PasswordUtils.HASH_LENGTH]; - Argon2BytesGenerator generator = new Argon2BytesGenerator(); - generator.init(parameters); - generator.generateBytes(password.getValue().getBytes(StandardCharsets.UTF_8), newHash); - - return PasswordUtils.constantTimeArrayEquals(newHash, originalHash); - } catch (Exception e) { - return CryptoUtils.createError("Error occurred while verifying password: " + e.getMessage()); - } - } - - /** - * Generate a salt string for Argon2 with default parameters. - * - * @return formatted salt string or error - */ - public static Object generateSaltArgon2() { - return generateSaltArgon2(PasswordUtils.DEFAULT_ITERATIONS, PasswordUtils.DEFAULT_MEMORY, - PasswordUtils.DEFAULT_PARALLELISM); - } - - /** - * Generate a salt string for Argon2 with custom parameters. - * - * @param iterations number of iterations - * @param memory memory usage in KB - * @param parallelism number of parallel threads - * @return formatted salt string or error - */ - public static Object generateSaltArgon2(long iterations, long memory, long parallelism) { - try { - byte[] salt = PasswordUtils.generateRandomSalt(); - String saltBase64 = Base64.toBase64String(salt); - - String result = PasswordUtils.formatArgon2Salt(memory, iterations, parallelism, saltBase64); - - return StringUtils.fromString(result); - } catch (Exception e) { - return CryptoUtils.createError("Error occurred while generating Argon2 salt: " + e.getMessage()); - } - } -} From d5744d5932ce776a1678eb9c20fddc8bee399f9e Mon Sep 17 00:00:00 2001 From: Randil Date: Mon, 17 Mar 2025 22:10:54 +0530 Subject: [PATCH 03/26] Add PBKDF2 configuration and validation methods in PasswordUtils --- .../stdlib/crypto/PasswordUtils.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java index 2cdcd7f0..38f094c2 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java @@ -68,6 +68,31 @@ public class PasswordUtils { */ public static final int HASH_LENGTH = 32; + /** + * Default number of iterations for PBKDF2. + */ + public static final int DEFAULT_PBKDF2_ITERATIONS = 210000; + + /** + * Minimum number of iterations for PBKDF2. + */ + public static final int MIN_PBKDF2_ITERATIONS = 10000; + + /** + * Length of the generated hash in bytes for PBKDF2. + */ + public static final int PBKDF2_HASH_LENGTH = 32; + + /** + * Default HMAC algorithm for PBKDF2. + */ + public static final String DEFAULT_PBKDF2_ALGORITHM = "SHA256"; + + /** + * Supported HMAC algorithms for PBKDF2. + */ + public static final String[] SUPPORTED_PBKDF2_ALGORITHMS = {"SHA1", "SHA256", "SHA512"}; + /** * Secure random number generator for salt generation. */ @@ -89,6 +114,39 @@ public static Object validateWorkFactor(long workFactor) { } return null; } + + /** + * Validate if the provided PBKDF2 iterations is within acceptable bounds. + * + * @param iterations the iterations count to validate + * @return null if valid, error if invalid + */ + public static Object validatePBKDF2Iterations(long iterations) { + if (iterations < MIN_PBKDF2_ITERATIONS) { + return CryptoUtils.createError( + String.format("Iterations must be at least %d", MIN_PBKDF2_ITERATIONS) + ); + } + return null; + } + + /** + * Validate if the provided PBKDF2 algorithm is supported. + * + * @param algorithm the HMAC algorithm to validate + * @return null if valid, error if invalid + */ + public static Object validatePBKDF2Algorithm(String algorithm) { + for (String supportedAlg : SUPPORTED_PBKDF2_ALGORITHMS) { + if (supportedAlg.equalsIgnoreCase(algorithm)) { + return null; + } + } + return CryptoUtils.createError( + String.format("Unsupported algorithm. Must be one of: %s", + String.join(", ", SUPPORTED_PBKDF2_ALGORITHMS)) + ); + } /** * Generate a cryptographically secure random salt. @@ -142,6 +200,34 @@ public static String formatArgon2Salt(long memory, long iterations, long paralle return String.format(Locale.ROOT, "$argon2id$v=19$m=%d,t=%d,p=%d$%s", memory, iterations, parallelism, saltBase64); } + + /** + * Format a PBKDF2 hash string according to the standard format. + * + * @param algorithm the HMAC algorithm used + * @param iterations iteration count + * @param saltBase64 Base64 encoded salt + * @param hashBase64 Base64 encoded hash + * @return formatted PBKDF2 hash string + */ + public static String formatPBKDF2Hash(String algorithm, long iterations, + String saltBase64, String hashBase64) { + return String.format(Locale.ROOT, "$pbkdf2-%s$i=%d$%s$%s", + algorithm.toLowerCase(Locale.ROOT), iterations, saltBase64, hashBase64); + } + + /** + * Format a PBKDF2 salt string according to the standard format. + * + * @param algorithm the HMAC algorithm used + * @param iterations iteration count + * @param saltBase64 Base64 encoded salt + * @return formatted PBKDF2 salt string + */ + public static String formatPBKDF2Salt(String algorithm, long iterations, String saltBase64) { + return String.format(Locale.ROOT, "$pbkdf2-%s$i=%d$%s", + algorithm.toLowerCase(Locale.ROOT), iterations, saltBase64); + } /** * Combine salt and hash byte arrays into a single array. From 719fa079d9c336d74dd3eaf1db000a90222573c5 Mon Sep 17 00:00:00 2001 From: Randil Date: Tue, 18 Mar 2025 09:32:06 +0530 Subject: [PATCH 04/26] Implement PBKDF2 hashing and verification methods in the crypto module --- ballerina/hash.bal | 43 ++++ ballerina/tests/hash_test.bal | 150 ++++++++++++++ .../stdlib/crypto/PasswordUtils.java | 2 +- .../stdlib/crypto/nativeimpl/Password.java | 186 +++++++++++++++++- 4 files changed, 379 insertions(+), 2 deletions(-) diff --git a/ballerina/hash.bal b/ballerina/hash.bal index 762dd222..e309efe1 100644 --- a/ballerina/hash.bal +++ b/ballerina/hash.bal @@ -179,3 +179,46 @@ public isolated function verifyArgon2(string password, string hashedPassword) re name: "verifyPasswordArgon2", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" } external; + +# Returns a PBKDF2 hash of the given password with optional parameters. +# ```ballerina +# string password = "mySecurePassword123"; +# string|crypto:Error hash = crypto:hashPbkdf2(password); +# ``` +# +# + password - Password string to be hashed +# + iterations - Optional number of iterations. Default is 10000 +# + algorithm - Optional HMAC algorithm (SHA1, SHA256, SHA512). Default is SHA256 +# + return - PBKDF2 hashed password string or Error if hashing fails +public isolated function hashPbkdf2(string password, int iterations = 10000, string algorithm = "SHA256") returns string|Error = @java:Method { + name: "hashPasswordPBKDF2", + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" +} external; + +# Verifies if a password matches a PBKDF2 hashed password. +# ```ballerina +# string password = "mySecurePassword123"; +# string hashedPassword = "$pbkdf2-sha256$i=10000$salt$hash"; +# boolean|crypto:Error matches = crypto:verifyPbkdf2(password, hashedPassword); +# ``` +# +# + password - Password string to verify +# + hashedPassword - PBKDF2 hashed password to verify against +# + return - Boolean indicating if password matches or Error if verification fails +public isolated function verifyPbkdf2(string password, string hashedPassword) returns boolean|Error = @java:Method { + name: "verifyPasswordPBKDF2", + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" +} external; + +# Generates a salt string for PBKDF2 with optional parameters. +# ```ballerina +# string|crypto:Error salt = crypto:generateSaltPbkdf2(); +# ``` +# +# + iterations - Optional number of iterations. Default is 10000 +# + algorithm - Optional HMAC algorithm (SHA1, SHA256, SHA512). Default is SHA256 +# + return - Formatted salt string or Error if generation fails +public isolated function generateSaltPbkdf2(int iterations = 10000, string algorithm = "SHA256") returns string|Error = @java:Method { + name: "generateSaltPBKDF2", + 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" +} external; \ No newline at end of file diff --git a/ballerina/tests/hash_test.bal b/ballerina/tests/hash_test.bal index 83543b6d..0468e726 100644 --- a/ballerina/tests/hash_test.bal +++ b/ballerina/tests/hash_test.bal @@ -336,6 +336,121 @@ isolated function testEmptyPasswordError(string algorithm) returns error? { test:assertEquals(hash.message(), "Password cannot be empty"); } +// tests for PBKDF2 +@test:Config {} +isolated function testHashPasswordPbkdf2Default() returns error? { + string password = "Ballerina@123"; + string hash = check hashPbkdf2(password); + test:assertTrue(hash.startsWith("$pbkdf2-sha256$i=10000$")); + test:assertTrue(hash.length() > 50); +} + +@test:Config {} +isolated function testHashPasswordPbkdf2Custom() returns error? { + string password = "Ballerina@123"; + string hash = check hashPbkdf2(password, 15000, "SHA512"); + test:assertTrue(hash.includes("$pbkdf2-sha512$i=15000$")); + test:assertTrue(hash.length() > 50); +} + +@test:Config { + dataProvider: complexPasswordsDataProvider +} +isolated function testHashPasswordPbkdf2ComplexPasswords(ComplexPassword data) returns error? { + string hash = check hashPbkdf2(data.password); + test:assertTrue(hash.startsWith("$pbkdf2-sha256$i=10000$")); + boolean result = check verifyPbkdf2(data.password, hash); + test:assertTrue(result, "Password verification failed for: " + data.password); +} + +@test:Config { + dataProvider: invalidPbkdf2ParamsDataProvider +} +isolated function testHashPasswordPbkdf2InvalidParams(InvalidPbkdf2Params data) { + string password = "Ballerina@123"; + string|Error hash = hashPbkdf2(password, data.iterations, data.algorithm); + if hash !is Error { + test:assertFail(string `Should fail with invalid parameters: iterations=${data.iterations}, algorithm=${data.algorithm}`); + } + test:assertEquals(hash.message(), data.expectedError); +} + +@test:Config { + dataProvider: validPasswordsDataProvider +} +isolated function testVerifyPasswordPbkdf2Success(ValidPassword data) returns error? { + string hash = check hashPbkdf2(data.password); + boolean result = check verifyPbkdf2(data.password, hash); + test:assertTrue(result, "Password verification failed for: " + data.password); +} + +@test:Config { + dataProvider: wrongPasswordsDataProvider +} +isolated function testVerifyPasswordPbkdf2Failure(PasswordPair data) returns error? { + string hash = check hashPbkdf2(data.correctPassword); + boolean result = check verifyPbkdf2(data.wrongPassword, hash); + test:assertFalse(result, "Should fail for wrong password: " + data.wrongPassword); +} + +@test:Config { + dataProvider: invalidPbkdf2HashesDataProvider +} +isolated function testVerifyPasswordPbkdf2InvalidHashFormat(InvalidHash data) { + string password = "Ballerina@123"; + boolean|Error result = verifyPbkdf2(password, data.hash); + if result !is Error { + test:assertFail("Should fail with invalid hash: " + data.hash); + } + test:assertTrue(result.message().startsWith(data.expectedError)); +} + +@test:Config { + dataProvider: uniquenessPasswordsDataProvider +} +isolated function testPbkdf2PasswordHashUniqueness(ValidPassword data) returns error? { + string hash1 = check hashPbkdf2(data.password); + string hash2 = check hashPbkdf2(data.password); + string hash3 = check hashPbkdf2(data.password); + + test:assertNotEquals(hash1, hash2, "Hashes should be unique for: " + data.password); + test:assertNotEquals(hash2, hash3, "Hashes should be unique for: " + data.password); + test:assertNotEquals(hash1, hash3, "Hashes should be unique for: " + data.password); + + boolean verify1 = check verifyPbkdf2(data.password, hash1); + boolean verify2 = check verifyPbkdf2(data.password, hash2); + boolean verify3 = check verifyPbkdf2(data.password, hash3); + + test:assertTrue(verify1 && verify2 && verify3, + "All hashes should verify successfully for: " + data.password); +} + +@test:Config { + dataProvider: pbkdf2AlgorithmsDataProvider +} +isolated function testPbkdf2DifferentAlgorithms(string algorithm) returns error? { + string password = "Ballerina@123"; + string hash = check hashPbkdf2(password, 10000, algorithm); + + test:assertTrue(hash.startsWith("$pbkdf2-" + algorithm.toLowerAscii() + "$"), + "Hash should start with correct algorithm identifier"); + + boolean result = check verifyPbkdf2(password, hash); + test:assertTrue(result, "Password verification failed for algorithm: " + algorithm); +} + +@test:Config {} +isolated function testPbkdf2GenerateSaltDefault() returns error? { + string salt = check generateSaltPbkdf2(); + test:assertTrue(salt.startsWith("$pbkdf2-sha256$i=10000$")); +} + +@test:Config {} +isolated function testPbkdf2GenerateSaltCustom() returns error? { + string salt = check generateSaltPbkdf2(12000, "SHA512"); + test:assertTrue(salt.startsWith("$pbkdf2-sha512$i=12000$")); +} + // data Providers for password tests isolated function complexPasswordsDataProvider() returns ComplexPassword[][] { return [ @@ -429,3 +544,38 @@ isolated function hashingAlgorithmsDataProvider() returns string[][] { ["bcrypt"] ]; } + +// Additional data providers for PBKDF2 tests +isolated function invalidPbkdf2ParamsDataProvider() returns InvalidPbkdf2Params[][] { + return [ + [{iterations: 0, algorithm: "SHA256", expectedError: "Iterations must be at least 1000"}], + [{iterations: 500, algorithm: "SHA256", expectedError: "Iterations must be at least 1000"}], + [{iterations: 10000, algorithm: "MD5", expectedError: "Unsupported algorithm: MD5. Must be one of: SHA1, SHA256, SHA512"}], + [{iterations: 10000, algorithm: "invalid-alg", expectedError: "Unsupported algorithm: invalid-alg. Must be one of: SHA1, SHA256, SHA512"}], + [{iterations: -100, algorithm: "SHA256", expectedError: "Iterations must be at least 1000"}] + ]; +} + +isolated function invalidPbkdf2HashesDataProvider() returns InvalidHash[][] { + return [ + [{hash: "invalid_hash_format", expectedError: "Invalid PBKDF2 hash format"}], + [{hash: "$pbkdf2-sha256$invalid", expectedError: "Invalid PBKDF2 hash format"}], + [{hash: "$pbkdf2$i=10000$salt$hash", expectedError: "Invalid PBKDF2 hash format"}], + [{hash: "$pbkdf2-md5$i=10000$salt$hash", expectedError: "Invalid PBKDF2 hash format"}] + ]; +} + +isolated function pbkdf2AlgorithmsDataProvider() returns string[][] { + return [ + ["SHA1"], + ["SHA256"], + ["SHA512"] + ]; +} + +// Record for invalid PBKDF2 parameters +type InvalidPbkdf2Params record {| + int iterations; + string algorithm; + string expectedError; +|}; \ No newline at end of file diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java index 38f094c2..9d385a67 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java @@ -91,7 +91,7 @@ public class PasswordUtils { /** * Supported HMAC algorithms for PBKDF2. */ - public static final String[] SUPPORTED_PBKDF2_ALGORITHMS = {"SHA1", "SHA256", "SHA512"}; + static final String[] SUPPORTED_PBKDF2_ALGORITHMS = {"SHA1", "SHA256", "SHA512"}; /** * Secure random number generator for salt generation. diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java index 89465588..03d80a55 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java @@ -27,11 +27,19 @@ import org.bouncycastle.util.encoders.Base64; import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; /** * Native implementation of password hashing functions. * Provides methods for hashing passwords, verifying hashes, and generating salts using - * the BCrypt and Argon2id algorithms. + * the BCrypt, Argon2id, and PBKDF2 algorithms. */ public class Password { @@ -279,4 +287,180 @@ public static Object generateSaltArgon2(long iterations, long memory, long paral return CryptoUtils.createError("Error occurred while generating Argon2 salt: " + e.getMessage()); } } + + // PBKDF2 methods + + /** + * Hash a password using PBKDF2 with default parameters. + * + * @param password the password to hash + * @return hashed password string or error + */ + public static Object hashPasswordPBKDF2(BString password) { + return hashPasswordPBKDF2(password, PasswordUtils.DEFAULT_PBKDF2_ITERATIONS, + StringUtils.fromString(PasswordUtils.DEFAULT_PBKDF2_ALGORITHM)); + } + + /** + * Hash a password using PBKDF2 with custom parameters. + * + * @param password the password to hash + * @param iterations number of iterations + * @param algorithm HMAC algorithm to use (SHA1, SHA256, SHA512) + * @return hashed password string or error + */ + public static Object hashPasswordPBKDF2(BString password, long iterations, BString algorithm) { + try { + String alg = algorithm.getValue(); + + Object validationError = PasswordUtils.validatePBKDF2Iterations(iterations); + if (validationError != null) { + return validationError; + } + + validationError = PasswordUtils.validatePBKDF2Algorithm(alg); + if (validationError != null) { + return validationError; + } + + if (password.getValue().length() == 0) { + return CryptoUtils.createError("Password cannot be empty"); + } + + byte[] salt = PasswordUtils.generateRandomSalt(); + + byte[] hash = generatePBKDF2Hash(password.getValue(), salt, (int) iterations, alg); + + String saltBase64 = Base64.toBase64String(salt); + String hashBase64 = Base64.toBase64String(hash); + + String result = PasswordUtils.formatPBKDF2Hash(alg, iterations, saltBase64, hashBase64); + + return StringUtils.fromString(result); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + return CryptoUtils.createError("Error occurred while hashing password with PBKDF2: " + e.getMessage()); + } catch (RuntimeException e) { + return CryptoUtils.createError("Unexpected error: " + e.getMessage()); + } catch (Exception e) { + return CryptoUtils.createError("Error occurred while hashing password with PBKDF2: " + e.getMessage()); + } + } + + /** + * Verify a password against a PBKDF2 hash. + * + * @param password the password to verify + * @param hashedPassword the hashed password to verify against + * @return true if password matches, false if not, or error if verification fails + */ + public static Object verifyPasswordPBKDF2(BString password, BString hashedPassword) { + try { + String hash = hashedPassword.getValue(); + Pattern pattern = Pattern.compile("\\$pbkdf2-(\\w+)\\$i=(\\d+)\\$(\\w+)\\$(\\w+)"); + Matcher matcher = pattern.matcher(hash); + + if (!matcher.matches()) { + return CryptoUtils.createError("Invalid PBKDF2 hash format"); + } + + String algorithm = matcher.group(1).toUpperCase(Locale.ROOT); + int iterations = Integer.parseInt(matcher.group(2)); + byte[] salt = Base64.decode(matcher.group(3)); + byte[] originalHash = Base64.decode(matcher.group(4)); + + byte[] newHash = generatePBKDF2Hash(password.getValue(), salt, iterations, algorithm); + + return PasswordUtils.constantTimeArrayEquals(newHash, originalHash); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + return CryptoUtils.createError("Error occurred while verifying password: " + e.getMessage()); + } catch (IllegalArgumentException e) { + return CryptoUtils.createError("Invalid hash format: " + e.getMessage()); + } catch (RuntimeException e) { + return CryptoUtils.createError("Unexpected error: " + e.getMessage()); + } catch (Exception e) { + return CryptoUtils.createError("Error occurred while verifying password: " + e.getMessage()); + } + } + + /** + * Generate a salt string for PBKDF2 with default parameters. + * + * @return formatted salt string or error + */ + public static Object generateSaltPBKDF2() { + return generateSaltPBKDF2(PasswordUtils.DEFAULT_PBKDF2_ITERATIONS, + StringUtils.fromString(PasswordUtils.DEFAULT_PBKDF2_ALGORITHM)); + } + + /** + * Generate a salt string for PBKDF2 with custom parameters. + * + * @param iterations number of iterations + * @param algorithm HMAC algorithm to use (SHA1, SHA256, SHA512) + * @return formatted salt string or error + */ + public static Object generateSaltPBKDF2(long iterations, BString algorithm) { + try { + String alg = algorithm.getValue(); + + Object validationError = PasswordUtils.validatePBKDF2Iterations(iterations); + if (validationError != null) { + return validationError; + } + + validationError = PasswordUtils.validatePBKDF2Algorithm(alg); + if (validationError != null) { + return validationError; + } + + byte[] salt = PasswordUtils.generateRandomSalt(); + String saltBase64 = Base64.toBase64String(salt); + + String result = PasswordUtils.formatPBKDF2Salt(alg, iterations, saltBase64); + + return StringUtils.fromString(result); + } catch (Exception e) { + return CryptoUtils.createError("Error occurred while generating PBKDF2 salt: " + e.getMessage()); + } + } + + /** + * Generate PBKDF2 hash using the given parameters. + * + * @param password password to hash + * @param salt salt to use + * @param iterations number of iterations + * @param algorithm HMAC algorithm to use (SHA1, SHA256, SHA512) + * @return hash byte array + * @throws NoSuchAlgorithmException if algorithm is not available + * @throws InvalidKeySpecException if key specification is invalid + */ + private static byte[] generatePBKDF2Hash(String password, byte[] salt, int iterations, String algorithm) + throws NoSuchAlgorithmException, InvalidKeySpecException { + + String hmacAlgorithm = algorithm.toUpperCase(Locale.ROOT); + String pbkdf2Algorithm; + int keyLength; + + switch (hmacAlgorithm) { + case "SHA1": + pbkdf2Algorithm = "PBKDF2WithHmacSHA1"; + keyLength = 20; // SHA-1 produces 160-bit (20-byte) hash + break; + case "SHA256": + pbkdf2Algorithm = "PBKDF2WithHmacSHA256"; + keyLength = 32; // SHA-256 produces 256-bit (32-byte) hash + break; + case "SHA512": + pbkdf2Algorithm = "PBKDF2WithHmacSHA512"; + keyLength = 64; // SHA-512 produces 512-bit (64-byte) hash + break; + default: + throw new NoSuchAlgorithmException("Unsupported algorithm: " + algorithm); + } + + PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLength * 8); + SecretKeyFactory factory = SecretKeyFactory.getInstance(pbkdf2Algorithm); + return factory.generateSecret(spec).getEncoded(); + } } From 8dd8018fc534727ffe0c4989d72aa68f1722b117 Mon Sep 17 00:00:00 2001 From: Randil Date: Tue, 18 Mar 2025 10:22:09 +0530 Subject: [PATCH 05/26] Update PBKDF2 parameters and validation in tests and PasswordUtils --- ballerina/tests/hash_test.bal | 23 +++++++++---------- .../stdlib/crypto/PasswordUtils.java | 2 +- .../stdlib/crypto/nativeimpl/Password.java | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/ballerina/tests/hash_test.bal b/ballerina/tests/hash_test.bal index 0468e726..b5f10dc4 100644 --- a/ballerina/tests/hash_test.bal +++ b/ballerina/tests/hash_test.bal @@ -46,6 +46,11 @@ type InvalidHash record {| string expectedError; |}; +type InvalidPbkdf2Params record {| + int iterations; + string algorithm; + string expectedError; +|}; @test:Config {} isolated function testHashCrc32() { @@ -548,11 +553,11 @@ isolated function hashingAlgorithmsDataProvider() returns string[][] { // Additional data providers for PBKDF2 tests isolated function invalidPbkdf2ParamsDataProvider() returns InvalidPbkdf2Params[][] { return [ - [{iterations: 0, algorithm: "SHA256", expectedError: "Iterations must be at least 1000"}], - [{iterations: 500, algorithm: "SHA256", expectedError: "Iterations must be at least 1000"}], - [{iterations: 10000, algorithm: "MD5", expectedError: "Unsupported algorithm: MD5. Must be one of: SHA1, SHA256, SHA512"}], - [{iterations: 10000, algorithm: "invalid-alg", expectedError: "Unsupported algorithm: invalid-alg. Must be one of: SHA1, SHA256, SHA512"}], - [{iterations: -100, algorithm: "SHA256", expectedError: "Iterations must be at least 1000"}] + [{iterations: 0, algorithm: "SHA256", expectedError: "Iterations must be at least 10000"}], + [{iterations: 500, algorithm: "SHA256", expectedError: "Iterations must be at least 10000"}], + [{iterations: 10000, algorithm: "MD5", expectedError: "Unsupported algorithm. Must be one of: SHA1, SHA256, SHA512"}], + [{iterations: 10000, algorithm: "invalid-alg", expectedError: "Unsupported algorithm. Must be one of: SHA1, SHA256, SHA512"}], + [{iterations: -100, algorithm: "SHA256", expectedError: "Iterations must be at least 10000"}] ]; } @@ -561,7 +566,7 @@ isolated function invalidPbkdf2HashesDataProvider() returns InvalidHash[][] { [{hash: "invalid_hash_format", expectedError: "Invalid PBKDF2 hash format"}], [{hash: "$pbkdf2-sha256$invalid", expectedError: "Invalid PBKDF2 hash format"}], [{hash: "$pbkdf2$i=10000$salt$hash", expectedError: "Invalid PBKDF2 hash format"}], - [{hash: "$pbkdf2-md5$i=10000$salt$hash", expectedError: "Invalid PBKDF2 hash format"}] + [{hash: "$pbkdf2-md5$i=10000$salt$hash", expectedError: "Error occurred while verifying password: Unsupported algorithm: MD5"}] ]; } @@ -573,9 +578,3 @@ isolated function pbkdf2AlgorithmsDataProvider() returns string[][] { ]; } -// Record for invalid PBKDF2 parameters -type InvalidPbkdf2Params record {| - int iterations; - string algorithm; - string expectedError; -|}; \ No newline at end of file diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java index 9d385a67..e93ef6b2 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java @@ -71,7 +71,7 @@ public class PasswordUtils { /** * Default number of iterations for PBKDF2. */ - public static final int DEFAULT_PBKDF2_ITERATIONS = 210000; + public static final int DEFAULT_PBKDF2_ITERATIONS = 10000; /** * Minimum number of iterations for PBKDF2. diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java index 03d80a55..b1dcff43 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java @@ -356,7 +356,7 @@ public static Object hashPasswordPBKDF2(BString password, long iterations, BStri public static Object verifyPasswordPBKDF2(BString password, BString hashedPassword) { try { String hash = hashedPassword.getValue(); - Pattern pattern = Pattern.compile("\\$pbkdf2-(\\w+)\\$i=(\\d+)\\$(\\w+)\\$(\\w+)"); + Pattern pattern = Pattern.compile("\\$pbkdf2-(\\w+)\\$i=(\\d+)\\$([A-Za-z0-9+/=]+)\\$([A-Za-z0-9+/=]+)"); Matcher matcher = pattern.matcher(hash); if (!matcher.matches()) { From 6a83815ed75522a572f453e4a41fcd5064648351 Mon Sep 17 00:00:00 2001 From: Randil Date: Tue, 18 Mar 2025 21:19:02 +0530 Subject: [PATCH 06/26] Remove generateSaltPbkdf2 function and related tests from hash module --- ballerina/hash.bal | 13 ------------- ballerina/tests/hash_test.bal | 12 ------------ 2 files changed, 25 deletions(-) diff --git a/ballerina/hash.bal b/ballerina/hash.bal index e309efe1..018d5a48 100644 --- a/ballerina/hash.bal +++ b/ballerina/hash.bal @@ -208,17 +208,4 @@ public isolated function hashPbkdf2(string password, int iterations = 10000, str public isolated function verifyPbkdf2(string password, string hashedPassword) returns boolean|Error = @java:Method { name: "verifyPasswordPBKDF2", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" -} external; - -# Generates a salt string for PBKDF2 with optional parameters. -# ```ballerina -# string|crypto:Error salt = crypto:generateSaltPbkdf2(); -# ``` -# -# + iterations - Optional number of iterations. Default is 10000 -# + algorithm - Optional HMAC algorithm (SHA1, SHA256, SHA512). Default is SHA256 -# + return - Formatted salt string or Error if generation fails -public isolated function generateSaltPbkdf2(int iterations = 10000, string algorithm = "SHA256") returns string|Error = @java:Method { - name: "generateSaltPBKDF2", - 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" } external; \ No newline at end of file diff --git a/ballerina/tests/hash_test.bal b/ballerina/tests/hash_test.bal index b5f10dc4..4b3cdf7a 100644 --- a/ballerina/tests/hash_test.bal +++ b/ballerina/tests/hash_test.bal @@ -444,18 +444,6 @@ isolated function testPbkdf2DifferentAlgorithms(string algorithm) returns error? test:assertTrue(result, "Password verification failed for algorithm: " + algorithm); } -@test:Config {} -isolated function testPbkdf2GenerateSaltDefault() returns error? { - string salt = check generateSaltPbkdf2(); - test:assertTrue(salt.startsWith("$pbkdf2-sha256$i=10000$")); -} - -@test:Config {} -isolated function testPbkdf2GenerateSaltCustom() returns error? { - string salt = check generateSaltPbkdf2(12000, "SHA512"); - test:assertTrue(salt.startsWith("$pbkdf2-sha512$i=12000$")); -} - // data Providers for password tests isolated function complexPasswordsDataProvider() returns ComplexPassword[][] { return [ From 37188f06a9a11db1862b7254382d97032cef90b3 Mon Sep 17 00:00:00 2001 From: Randil Date: Tue, 18 Mar 2025 21:24:32 +0530 Subject: [PATCH 07/26] Add PBKDF2 password hashing and verification support to changelog --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index 22bd71cc..eb0521cf 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,9 @@ This file contains all the notable changes done to the Ballerina Crypto package The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### Added +- [Introduce support for PBKDF2 password hashing and verification](https://github.com/ballerina-platform/ballerina-lang/issues/43926) + ## [Unreleased] ### Changed From 3c667ff5720ed3ae5b70701fb7902663e712421b Mon Sep 17 00:00:00 2001 From: Randil Date: Tue, 18 Mar 2025 21:26:28 +0530 Subject: [PATCH 08/26] Fix changelog, move the PBKDF2 support change to unreleased --- changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index eb0521cf..8c36c67b 100644 --- a/changelog.md +++ b/changelog.md @@ -3,11 +3,11 @@ This file contains all the notable changes done to the Ballerina Crypto package The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + ### Added - [Introduce support for PBKDF2 password hashing and verification](https://github.com/ballerina-platform/ballerina-lang/issues/43926) -## [Unreleased] - ### Changed - [Update OIDS of NIST approved post quantum algorithms](https://github.com/ballerina-platform/ballerina-library/issues/7678) From 095e2701af86bcb9b080c810cef3a76b5650b540 Mon Sep 17 00:00:00 2001 From: Randil Date: Tue, 18 Mar 2025 21:46:11 +0530 Subject: [PATCH 09/26] Add proposal for PBKDF2-based password hashing APIs in Ballerina crypto module --- docs/proposals/pbkdf2-hashing-apis.md | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/proposals/pbkdf2-hashing-apis.md diff --git a/docs/proposals/pbkdf2-hashing-apis.md b/docs/proposals/pbkdf2-hashing-apis.md new file mode 100644 index 00000000..ba9032d8 --- /dev/null +++ b/docs/proposals/pbkdf2-hashing-apis.md @@ -0,0 +1,47 @@ +# Proposal: Introduce PBKDF2-Based Password Hashing to Ballerina Crypto Module + +_Authors_: @randilt +_Reviewers_: +_Created_: 2025/03/18 +_Updated_: 2025/03/18 +_Issues_: [#43926](https://github.com/ballerina-platform/ballerina-lang/issues/43926) + +## Summary +The Ballerina crypto module currently lacks built-in support for PBKDF2 password hashing, a widely used key derivation function for secure password storage. This proposal introduces two new APIs to provide PBKDF2-based password hashing and verification. + +## Goals +- Introduce PBKDF2 password hashing support with configurable parameters +- Provide a verification function to check hashed passwords against user inputs +- Support common HMAC algorithms (`SHA1`, `SHA256`, `SHA512`) and iteration count customization + +## Motivation +Password hashing is a fundamental security requirement for authentication systems. PBKDF2 is a widely recognized key derivation function that enhances security by applying multiple iterations of a cryptographic hash function. By integrating PBKDF2 support, the Ballerina crypto module will offer a standardized and secure method for password storage and verification. + +## Description +This proposal aims to introduce secure PBKDF2 password hashing and verification capabilities in the Ballerina crypto module. + +### API Additions + +#### PBKDF2 Hashing Function +A new API will be introduced to generate PBKDF2 hashes with configurable parameters: + +```ballerina +public isolated function hashPbkdf2( + string password, + int iterations = 10000, + string algorithm = "SHA256" +) returns string|Error; +``` + +#### PBKDF2 Verification Function +A corresponding API will be introduced to verify a password against a PBKDF2 hash: + +```ballerina +public isolated function verifyPbkdf2( + string password, + string hashedPassword +) returns boolean|Error; +``` + +These functions will allow developers to securely hash and verify passwords using PBKDF2 with customizable parameters for increased security. + From af9ef355e02274cb8195226427c4fab84a9cab41 Mon Sep 17 00:00:00 2001 From: Randil Date: Tue, 18 Mar 2025 22:04:57 +0530 Subject: [PATCH 10/26] Add PBKDF2 password hashing and verification documentation to the specification --- docs/spec/spec.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 4fef99eb..32657f99 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -105,6 +105,7 @@ The conforming implementation of the specification is released and included in t 10. [Password hashing](#10-password-hashing) * 10.1 [BCrypt](#101-bcrypt) * 10.2 [Argon2](#102-argon2) + * 10.3 [PBKDF2](#103-pbkdf2) ## 1. [Overview](#1-overview) @@ -1183,4 +1184,41 @@ string hashedPassword1 = check crypto:hashArgon2(password); // Hash with custom parameters string hashedPassword2 = check crypto:hashArgon2(password, iterations = 4, memory = 131072, parallelism = 8); boolean isValid = check crypto:verifyArgon2(password, hashedPassword1); +### 10.3 [PBKDF2](#103-pbkdf2) + +Implements the PBKDF2 (Password-Based Key Derivation Function 2) algorithm for password hashing. + +```ballerina +public isolated function hashPbkdf2(string password, int iterations = 10000, + string algorithm = "SHA256") returns string|Error +``` + +Parameters: +- `password`: The plain text password to hash +- `iterations`: Optional number of iterations (default: 10000) +- `algorithm`: Optional HMAC algorithm (SHA1, SHA256, SHA512). Default is SHA256 + +Example: +```ballerina +string password = "mySecurePassword123"; +// Hash with default parameters +string hashedPassword = check crypto:hashPbkdf2(password); +// Hash with custom parameters +string customHashedPassword = check crypto:hashPbkdf2(password, iterations = 15000, algorithm = "SHA512"); +``` + +```ballerina +public isolated function verifyPbkdf2(string password, string hashedPassword) returns boolean|Error +``` + +Parameters: +- `password`: The plain text password to verify +- `hashedPassword`: PBKDF2 hashed password to verify against + +Example: +```ballerina +string password = "mySecurePassword123"; +string hashedPassword = "$pbkdf2-sha256$i=10000$salt$hash"; +// Verify the hashed password +boolean isValid = check crypto:verifyPbkdf2(password, hashedPassword); ``` From b0afd37faf25dff8dbad58177bad6c780e7d7069 Mon Sep 17 00:00:00 2001 From: Randil Date: Tue, 18 Mar 2025 22:07:35 +0530 Subject: [PATCH 11/26] Add PBKDF2 algorithm implementation to the specification --- docs/spec/spec.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 32657f99..5fbc7bc8 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -1184,6 +1184,7 @@ string hashedPassword1 = check crypto:hashArgon2(password); // Hash with custom parameters string hashedPassword2 = check crypto:hashArgon2(password, iterations = 4, memory = 131072, parallelism = 8); boolean isValid = check crypto:verifyArgon2(password, hashedPassword1); +``` ### 10.3 [PBKDF2](#103-pbkdf2) Implements the PBKDF2 (Password-Based Key Derivation Function 2) algorithm for password hashing. From be53244bb296cefeca4ad60059c336d6629ab181 Mon Sep 17 00:00:00 2001 From: Randil Tharusha <51049280+randilt@users.noreply.github.com> Date: Wed, 19 Mar 2025 13:16:51 +0530 Subject: [PATCH 12/26] Update ballerina/hash.bal Co-authored-by: DimuthuMadushan <35717653+DimuthuMadushan@users.noreply.github.com> --- ballerina/hash.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/hash.bal b/ballerina/hash.bal index 018d5a48..c5575061 100644 --- a/ballerina/hash.bal +++ b/ballerina/hash.bal @@ -208,4 +208,4 @@ public isolated function hashPbkdf2(string password, int iterations = 10000, str public isolated function verifyPbkdf2(string password, string hashedPassword) returns boolean|Error = @java:Method { name: "verifyPasswordPBKDF2", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" -} external; \ No newline at end of file +} external; From 49781ca7a28190d1ca0e1fa80d1128a915452eb4 Mon Sep 17 00:00:00 2001 From: Randil Date: Thu, 3 Apr 2025 12:44:52 +0530 Subject: [PATCH 13/26] Enhance PBKDF2 hashing function to have an enum for algorithm param --- ballerina/hash.bal | 2 +- .../java/io/ballerina/stdlib/crypto/nativeimpl/Password.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ballerina/hash.bal b/ballerina/hash.bal index 018d5a48..55aaf024 100644 --- a/ballerina/hash.bal +++ b/ballerina/hash.bal @@ -190,7 +190,7 @@ public isolated function verifyArgon2(string password, string hashedPassword) re # + iterations - Optional number of iterations. Default is 10000 # + algorithm - Optional HMAC algorithm (SHA1, SHA256, SHA512). Default is SHA256 # + return - PBKDF2 hashed password string or Error if hashing fails -public isolated function hashPbkdf2(string password, int iterations = 10000, string algorithm = "SHA256") returns string|Error = @java:Method { +public isolated function hashPbkdf2(string password, int iterations = 10000, "SHA1"|"SHA256"|"SHA512" algorithm = "SHA256") returns string|Error = @java:Method { name: "hashPasswordPBKDF2", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" } external; diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java index b1dcff43..29f5af35 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java @@ -458,7 +458,9 @@ private static byte[] generatePBKDF2Hash(String password, byte[] salt, int itera default: throw new NoSuchAlgorithmException("Unsupported algorithm: " + algorithm); } - + // PBEKeySpec requires keyLength in bits + // and the length of the key in bytes is keyLength / 8 + // so we need to multiply by 8 to get the key length in bits PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLength * 8); SecretKeyFactory factory = SecretKeyFactory.getInstance(pbkdf2Algorithm); return factory.generateSecret(spec).getEncoded(); From 096e08e8760da39966769dc14ee63a9334881d2e Mon Sep 17 00:00:00 2001 From: Randil Date: Thu, 3 Apr 2025 12:47:31 +0530 Subject: [PATCH 14/26] Update PBKDF2 hashing proposal hashPbkdf2 func --- docs/proposals/pbkdf2-hashing-apis.md | 37 +++++++++++++++------------ 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/docs/proposals/pbkdf2-hashing-apis.md b/docs/proposals/pbkdf2-hashing-apis.md index ba9032d8..cfcebf8d 100644 --- a/docs/proposals/pbkdf2-hashing-apis.md +++ b/docs/proposals/pbkdf2-hashing-apis.md @@ -1,39 +1,45 @@ # Proposal: Introduce PBKDF2-Based Password Hashing to Ballerina Crypto Module _Authors_: @randilt -_Reviewers_: +_Reviewers_: _Created_: 2025/03/18 _Updated_: 2025/03/18 -_Issues_: [#43926](https://github.com/ballerina-platform/ballerina-lang/issues/43926) +_Issues_: [#43926](https://github.com/ballerina-platform/ballerina-lang/issues/43926) + +## Summary -## Summary The Ballerina crypto module currently lacks built-in support for PBKDF2 password hashing, a widely used key derivation function for secure password storage. This proposal introduces two new APIs to provide PBKDF2-based password hashing and verification. -## Goals -- Introduce PBKDF2 password hashing support with configurable parameters -- Provide a verification function to check hashed passwords against user inputs -- Support common HMAC algorithms (`SHA1`, `SHA256`, `SHA512`) and iteration count customization +## Goals + +- Introduce PBKDF2 password hashing support with configurable parameters +- Provide a verification function to check hashed passwords against user inputs +- Support common HMAC algorithms (`SHA1`, `SHA256`, `SHA512`) and iteration count customization + +## Motivation + +Password hashing is a fundamental security requirement for authentication systems. PBKDF2 is a widely recognized key derivation function that enhances security by applying multiple iterations of a cryptographic hash function. By integrating PBKDF2 support, the Ballerina crypto module will offer a standardized and secure method for password storage and verification. -## Motivation -Password hashing is a fundamental security requirement for authentication systems. PBKDF2 is a widely recognized key derivation function that enhances security by applying multiple iterations of a cryptographic hash function. By integrating PBKDF2 support, the Ballerina crypto module will offer a standardized and secure method for password storage and verification. +## Description -## Description -This proposal aims to introduce secure PBKDF2 password hashing and verification capabilities in the Ballerina crypto module. +This proposal aims to introduce secure PBKDF2 password hashing and verification capabilities in the Ballerina crypto module. -### API Additions +### API Additions + +#### PBKDF2 Hashing Function -#### PBKDF2 Hashing Function A new API will be introduced to generate PBKDF2 hashes with configurable parameters: ```ballerina public isolated function hashPbkdf2( string password, int iterations = 10000, - string algorithm = "SHA256" + "SHA1"|"SHA256"|"SHA512" algorithm = "SHA256" ) returns string|Error; ``` -#### PBKDF2 Verification Function +#### PBKDF2 Verification Function + A corresponding API will be introduced to verify a password against a PBKDF2 hash: ```ballerina @@ -44,4 +50,3 @@ public isolated function verifyPbkdf2( ``` These functions will allow developers to securely hash and verify passwords using PBKDF2 with customizable parameters for increased security. - From 75e1ca2d861e5c5a48036566f0b5b16fdddf077e Mon Sep 17 00:00:00 2001 From: Randil Date: Thu, 3 Apr 2025 20:41:36 +0530 Subject: [PATCH 15/26] Add HmacAlgorithm enum for PBKDF2 and update related functions --- ballerina/hash.bal | 11 +++++++++-- ballerina/tests/hash_test.bal | 25 +++++++++++-------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/ballerina/hash.bal b/ballerina/hash.bal index b33231d2..c7c586cf 100644 --- a/ballerina/hash.bal +++ b/ballerina/hash.bal @@ -16,6 +16,13 @@ import ballerina/jballerina.java; +# Supported HMAC algorithms for PBKDF2 +public enum HmacAlgorithm { + SHA1, + SHA256, + SHA512 +} + # Returns the MD5 hash of the given data. # ```ballerina # string dataString = "Hello Ballerina"; @@ -190,7 +197,7 @@ public isolated function verifyArgon2(string password, string hashedPassword) re # + iterations - Optional number of iterations. Default is 10000 # + algorithm - Optional HMAC algorithm (SHA1, SHA256, SHA512). Default is SHA256 # + return - PBKDF2 hashed password string or Error if hashing fails -public isolated function hashPbkdf2(string password, int iterations = 10000, "SHA1"|"SHA256"|"SHA512" algorithm = "SHA256") returns string|Error = @java:Method { +public isolated function hashPbkdf2(string password, int iterations = 10000, HmacAlgorithm algorithm = "SHA256") returns string|Error = @java:Method { name: "hashPasswordPBKDF2", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" } external; @@ -208,4 +215,4 @@ public isolated function hashPbkdf2(string password, int iterations = 10000, "SH public isolated function verifyPbkdf2(string password, string hashedPassword) returns boolean|Error = @java:Method { name: "verifyPasswordPBKDF2", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" -} external; +} external; \ No newline at end of file diff --git a/ballerina/tests/hash_test.bal b/ballerina/tests/hash_test.bal index 4b3cdf7a..ab0704b7 100644 --- a/ballerina/tests/hash_test.bal +++ b/ballerina/tests/hash_test.bal @@ -48,7 +48,7 @@ type InvalidHash record {| type InvalidPbkdf2Params record {| int iterations; - string algorithm; + HmacAlgorithm algorithm; string expectedError; |}; @@ -433,15 +433,15 @@ isolated function testPbkdf2PasswordHashUniqueness(ValidPassword data) returns e @test:Config { dataProvider: pbkdf2AlgorithmsDataProvider } -isolated function testPbkdf2DifferentAlgorithms(string algorithm) returns error? { +isolated function testPbkdf2DifferentAlgorithms(HmacAlgorithm algorithm) returns error? { string password = "Ballerina@123"; string hash = check hashPbkdf2(password, 10000, algorithm); - test:assertTrue(hash.startsWith("$pbkdf2-" + algorithm.toLowerAscii() + "$"), + test:assertTrue(hash.startsWith("$pbkdf2-" + algorithm.toString().toLowerAscii() + "$"), "Hash should start with correct algorithm identifier"); boolean result = check verifyPbkdf2(password, hash); - test:assertTrue(result, "Password verification failed for algorithm: " + algorithm); + test:assertTrue(result, "Password verification failed for algorithm: " + algorithm.toString()); } // data Providers for password tests @@ -541,11 +541,9 @@ isolated function hashingAlgorithmsDataProvider() returns string[][] { // Additional data providers for PBKDF2 tests isolated function invalidPbkdf2ParamsDataProvider() returns InvalidPbkdf2Params[][] { return [ - [{iterations: 0, algorithm: "SHA256", expectedError: "Iterations must be at least 10000"}], - [{iterations: 500, algorithm: "SHA256", expectedError: "Iterations must be at least 10000"}], - [{iterations: 10000, algorithm: "MD5", expectedError: "Unsupported algorithm. Must be one of: SHA1, SHA256, SHA512"}], - [{iterations: 10000, algorithm: "invalid-alg", expectedError: "Unsupported algorithm. Must be one of: SHA1, SHA256, SHA512"}], - [{iterations: -100, algorithm: "SHA256", expectedError: "Iterations must be at least 10000"}] + [{iterations: 0, algorithm: SHA256, expectedError: "Iterations must be at least 10000"}], + [{iterations: 500, algorithm: SHA256, expectedError: "Iterations must be at least 10000"}], + [{iterations: -100, algorithm: SHA256, expectedError: "Iterations must be at least 10000"}] ]; } @@ -557,12 +555,11 @@ isolated function invalidPbkdf2HashesDataProvider() returns InvalidHash[][] { [{hash: "$pbkdf2-md5$i=10000$salt$hash", expectedError: "Error occurred while verifying password: Unsupported algorithm: MD5"}] ]; } - -isolated function pbkdf2AlgorithmsDataProvider() returns string[][] { +isolated function pbkdf2AlgorithmsDataProvider() returns HmacAlgorithm[][] { return [ - ["SHA1"], - ["SHA256"], - ["SHA512"] + [SHA1], + [SHA256], + [SHA512] ]; } From 92b2dba24f45711d843031da5e21c2794618e527 Mon Sep 17 00:00:00 2001 From: Randil Date: Thu, 3 Apr 2025 20:47:02 +0530 Subject: [PATCH 16/26] Fix algorithm parameter documentation and update default value in hashPbkdf2 function --- ballerina/hash.bal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/hash.bal b/ballerina/hash.bal index c7c586cf..099f0fa1 100644 --- a/ballerina/hash.bal +++ b/ballerina/hash.bal @@ -195,9 +195,9 @@ public isolated function verifyArgon2(string password, string hashedPassword) re # # + password - Password string to be hashed # + iterations - Optional number of iterations. Default is 10000 -# + algorithm - Optional HMAC algorithm (SHA1, SHA256, SHA512). Default is SHA256 +# + algorithm - Optional HMAC algorithm (`SHA1`, `SHA256`, `SHA512`). Default is SHA256 # + return - PBKDF2 hashed password string or Error if hashing fails -public isolated function hashPbkdf2(string password, int iterations = 10000, HmacAlgorithm algorithm = "SHA256") returns string|Error = @java:Method { +public isolated function hashPbkdf2(string password, int iterations = 10000, HmacAlgorithm algorithm = SHA256) returns string|Error = @java:Method { name: "hashPasswordPBKDF2", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" } external; From 94dbed392d055746c73272673f381fa1ddf8d717 Mon Sep 17 00:00:00 2001 From: Randil Date: Thu, 3 Apr 2025 21:06:38 +0530 Subject: [PATCH 17/26] Rename validatePBKDF2Algorithm method to validatePbkdf2Algorithm for consistency --- .../main/java/io/ballerina/stdlib/crypto/PasswordUtils.java | 2 +- .../java/io/ballerina/stdlib/crypto/nativeimpl/Password.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java index e93ef6b2..ce0f1231 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java @@ -136,7 +136,7 @@ public static Object validatePBKDF2Iterations(long iterations) { * @param algorithm the HMAC algorithm to validate * @return null if valid, error if invalid */ - public static Object validatePBKDF2Algorithm(String algorithm) { + public static Object validatePbkdf2Algorithm(String algorithm) { for (String supportedAlg : SUPPORTED_PBKDF2_ALGORITHMS) { if (supportedAlg.equalsIgnoreCase(algorithm)) { return null; diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java index 29f5af35..3e91aac1 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java @@ -318,7 +318,7 @@ public static Object hashPasswordPBKDF2(BString password, long iterations, BStri return validationError; } - validationError = PasswordUtils.validatePBKDF2Algorithm(alg); + validationError = PasswordUtils.validatePbkdf2Algorithm(alg); if (validationError != null) { return validationError; } @@ -408,7 +408,7 @@ public static Object generateSaltPBKDF2(long iterations, BString algorithm) { return validationError; } - validationError = PasswordUtils.validatePBKDF2Algorithm(alg); + validationError = PasswordUtils.validatePbkdf2Algorithm(alg); if (validationError != null) { return validationError; } From 176b24456a286ed18596c591959b61d643d9614e Mon Sep 17 00:00:00 2001 From: Randil Date: Thu, 3 Apr 2025 21:17:19 +0530 Subject: [PATCH 18/26] Add minimum memory cost constant for PBKDF2 and update validation logic --- .../main/java/io/ballerina/stdlib/crypto/PasswordUtils.java | 5 +++++ .../java/io/ballerina/stdlib/crypto/nativeimpl/Password.java | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java index ce0f1231..b6e8f2f6 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java @@ -87,6 +87,11 @@ public class PasswordUtils { * Default HMAC algorithm for PBKDF2. */ public static final String DEFAULT_PBKDF2_ALGORITHM = "SHA256"; + + /** + * Minimum allowed memory cost for PBKDF2. + */ + public static final int PBKDF2_MIN_MEMORY_COST = 8192; /** * Supported HMAC algorithms for PBKDF2. diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java index 3e91aac1..f94e5a22 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java @@ -177,8 +177,9 @@ public static Object hashPasswordArgon2(BString password, long iterations, long if (iterations <= 0) { return CryptoUtils.createError("Iterations must be positive"); } - if (memory < 8192) { - return CryptoUtils.createError("Memory must be at least 8192 KB (8MB)"); + if (memory < PasswordUtils.PBKDF2_MIN_MEMORY_COST) { + return CryptoUtils.createError(String.format("Memory must be at least %d KB (%d MB)", + PasswordUtils.PBKDF2_MIN_MEMORY_COST, PasswordUtils.PBKDF2_MIN_MEMORY_COST / 1024)); } if (parallelism <= 0) { return CryptoUtils.createError("Parallelism must be positive"); From 85a6f0f282d5e30032ffb17a7972c5f47cb9ec56 Mon Sep 17 00:00:00 2001 From: Randil Date: Thu, 3 Apr 2025 21:21:07 +0530 Subject: [PATCH 19/26] Fix formatting in error message for minimum memory cost in PBKDF2 validation --- .../java/io/ballerina/stdlib/crypto/nativeimpl/Password.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java index f94e5a22..509df04a 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java @@ -178,7 +178,7 @@ public static Object hashPasswordArgon2(BString password, long iterations, long return CryptoUtils.createError("Iterations must be positive"); } if (memory < PasswordUtils.PBKDF2_MIN_MEMORY_COST) { - return CryptoUtils.createError(String.format("Memory must be at least %d KB (%d MB)", + return CryptoUtils.createError(String.format("Memory must be at least %d KB (%dMB)", PasswordUtils.PBKDF2_MIN_MEMORY_COST, PasswordUtils.PBKDF2_MIN_MEMORY_COST / 1024)); } if (parallelism <= 0) { From 709dbc3800c5537a822faaa95ad24d1f221064c5 Mon Sep 17 00:00:00 2001 From: Randil Date: Thu, 3 Apr 2025 22:16:22 +0530 Subject: [PATCH 20/26] Refactor error messages in Argon2 and Bcrypt tests to use string interpolation for improved readability --- ballerina/tests/hash_test.bal | 61 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/ballerina/tests/hash_test.bal b/ballerina/tests/hash_test.bal index ab0704b7..ebaef06e 100644 --- a/ballerina/tests/hash_test.bal +++ b/ballerina/tests/hash_test.bal @@ -139,7 +139,6 @@ isolated function testHashSha512WithSalt() { test:assertEquals(hashSha512(input, salt).toBase16(), expectedSha512Hash); } - @test:Config {} isolated function testHashKeccak256() { byte[] input = "Ballerina test".toBytes(); @@ -172,7 +171,7 @@ isolated function testHashPasswordArgon2ComplexPasswords(ComplexPassword data) r string hash = check hashArgon2(data.password); test:assertTrue(hash.startsWith("$argon2id$v=19$")); boolean result = check verifyArgon2(data.password, hash); - test:assertTrue(result, "Password verification failed for: " + data.password); + test:assertTrue(result, string `Password verification failed for: ${data.password}`); } @test:Config { @@ -193,7 +192,7 @@ isolated function testHashPasswordArgon2InvalidParams(InvalidArgon2Params data) isolated function testVerifyPasswordArgon2Success(ValidPassword data) returns error? { string hash = check hashArgon2(data.password); boolean result = check verifyArgon2(data.password, hash); - test:assertTrue(result, "Password verification failed for: " + data.password); + test:assertTrue(result, string `Password verification failed for: ${data.password}`); } @test:Config { @@ -202,7 +201,7 @@ isolated function testVerifyPasswordArgon2Success(ValidPassword data) returns er isolated function testVerifyPasswordArgon2Failure(PasswordPair data) returns error? { string hash = check hashArgon2(data.correctPassword); boolean result = check verifyArgon2(data.wrongPassword, hash); - test:assertFalse(result, "Should fail for wrong password: " + data.wrongPassword); + test:assertFalse(result, string `Should fail for wrong password: ${data.wrongPassword}`); } @test:Config { @@ -212,7 +211,7 @@ isolated function testVerifyPasswordArgon2InvalidHashFormat(InvalidHash data) { string password = "Ballerina@123"; boolean|Error result = verifyArgon2(password, data.hash); if result !is Error { - test:assertFail("Should fail with invalid hash: " + data.hash); + test:assertFail(string `Should fail with invalid hash: ${data.hash}`); } test:assertTrue(result.message().startsWith("Invalid Argon2 hash format")); } @@ -225,16 +224,16 @@ isolated function testArgon2PasswordHashUniqueness(ValidPassword data) returns e string hash2 = check hashArgon2(data.password); string hash3 = check hashArgon2(data.password); - test:assertNotEquals(hash1, hash2, "Hashes should be unique for: " + data.password); - test:assertNotEquals(hash2, hash3, "Hashes should be unique for: " + data.password); - test:assertNotEquals(hash1, hash3, "Hashes should be unique for: " + data.password); + test:assertNotEquals(hash1, hash2, string `Hashes should be unique for: ${data.password}`); + test:assertNotEquals(hash2, hash3, string `Hashes should be unique for: ${data.password}`); + test:assertNotEquals(hash1, hash3, string `Hashes should be unique for: ${data.password}`); boolean verify1 = check verifyArgon2(data.password, hash1); boolean verify2 = check verifyArgon2(data.password, hash2); boolean verify3 = check verifyArgon2(data.password, hash3); test:assertTrue(verify1 && verify2 && verify3, - "All hashes should verify successfully for: " + data.password); + string `All hashes should verify successfully for: ${data.password}`); } // tests for Bcrypt @@ -263,7 +262,7 @@ isolated function testHashPasswordBcryptComplexPasswords(ComplexPassword data) r test:assertTrue(hash.length() > 50); boolean result = check verifyBcrypt(data.password, hash); - test:assertTrue(result, "Password verification failed for: " + data.password); + test:assertTrue(result, string `Password verification failed for: ${data.password}`); } @test:Config { @@ -284,7 +283,7 @@ isolated function testHashPasswordBcryptInvalidWorkFactor(InvalidWorkFactor data isolated function testVerifyPasswordBcryptSuccess(ValidPassword data) returns error? { string hash = check hashBcrypt(data.password); boolean result = check verifyBcrypt(data.password, hash); - test:assertTrue(result, "Password verification failed for: " + data.password); + test:assertTrue(result, string `Password verification failed for: ${data.password}`); } @test:Config { @@ -293,7 +292,7 @@ isolated function testVerifyPasswordBcryptSuccess(ValidPassword data) returns er isolated function testVerifyPasswordBcryptFailure(PasswordPair data) returns error? { string hash = check hashBcrypt(data.correctPassword); boolean result = check verifyBcrypt(data.wrongPassword, hash); - test:assertFalse(result, "Should fail for wrong password: " + data.wrongPassword); + test:assertFalse(result, string `Should fail for wrong password: ${data.wrongPassword}`); } @test:Config { @@ -303,7 +302,7 @@ isolated function testVerifyPasswordBcryptInvalidHashFormat(InvalidHash data) { string password = "Ballerina@123"; boolean|Error result = verifyBcrypt(password, data.hash); if result !is Error { - test:assertFail("Should fail with invalid hash: " + data.hash); + test:assertFail(string `Should fail with invalid hash: ${data.hash}`); } test:assertEquals(result.message(), data.expectedError); } @@ -316,16 +315,16 @@ isolated function testBcryptPasswordHashUniqueness(ValidPassword data) returns e string hash2 = check hashBcrypt(data.password); string hash3 = check hashBcrypt(data.password); - test:assertNotEquals(hash1, hash2, "Hashes should be unique for: " + data.password); - test:assertNotEquals(hash2, hash3, "Hashes should be unique for: " + data.password); - test:assertNotEquals(hash1, hash3, "Hashes should be unique for: " + data.password); + test:assertNotEquals(hash1, hash2, string `Hashes should be unique for: ${data.password}`); + test:assertNotEquals(hash2, hash3, string `Hashes should be unique for: ${data.password}`); + test:assertNotEquals(hash1, hash3, string `Hashes should be unique for: ${data.password}`); boolean verify1 = check verifyBcrypt(data.password, hash1); boolean verify2 = check verifyBcrypt(data.password, hash2); boolean verify3 = check verifyBcrypt(data.password, hash3); test:assertTrue(verify1 && verify2 && verify3, - "All hashes should verify successfully for: " + data.password); + string `All hashes should verify successfully for: ${data.password}`); } // common tests for both algorithms @@ -353,8 +352,8 @@ isolated function testHashPasswordPbkdf2Default() returns error? { @test:Config {} isolated function testHashPasswordPbkdf2Custom() returns error? { string password = "Ballerina@123"; - string hash = check hashPbkdf2(password, 15000, "SHA512"); - test:assertTrue(hash.includes("$pbkdf2-sha512$i=15000$")); + string hash = check hashPbkdf2(password, 15000, SHA512); + test:assertTrue(hash.startsWith(string `$pbkdf2-sha512$i=15000$`)); test:assertTrue(hash.length() > 50); } @@ -365,7 +364,7 @@ isolated function testHashPasswordPbkdf2ComplexPasswords(ComplexPassword data) r string hash = check hashPbkdf2(data.password); test:assertTrue(hash.startsWith("$pbkdf2-sha256$i=10000$")); boolean result = check verifyPbkdf2(data.password, hash); - test:assertTrue(result, "Password verification failed for: " + data.password); + test:assertTrue(result, string `Password verification failed for: ${data.password}`); } @test:Config { @@ -386,7 +385,7 @@ isolated function testHashPasswordPbkdf2InvalidParams(InvalidPbkdf2Params data) isolated function testVerifyPasswordPbkdf2Success(ValidPassword data) returns error? { string hash = check hashPbkdf2(data.password); boolean result = check verifyPbkdf2(data.password, hash); - test:assertTrue(result, "Password verification failed for: " + data.password); + test:assertTrue(result, string `Password verification failed for: ${data.password}`); } @test:Config { @@ -395,7 +394,7 @@ isolated function testVerifyPasswordPbkdf2Success(ValidPassword data) returns er isolated function testVerifyPasswordPbkdf2Failure(PasswordPair data) returns error? { string hash = check hashPbkdf2(data.correctPassword); boolean result = check verifyPbkdf2(data.wrongPassword, hash); - test:assertFalse(result, "Should fail for wrong password: " + data.wrongPassword); + test:assertFalse(result, string `Should fail for wrong password: ${data.wrongPassword}`); } @test:Config { @@ -405,7 +404,7 @@ isolated function testVerifyPasswordPbkdf2InvalidHashFormat(InvalidHash data) { string password = "Ballerina@123"; boolean|Error result = verifyPbkdf2(password, data.hash); if result !is Error { - test:assertFail("Should fail with invalid hash: " + data.hash); + test:assertFail(string `Should fail with invalid hash: ${data.hash}`); } test:assertTrue(result.message().startsWith(data.expectedError)); } @@ -418,16 +417,16 @@ isolated function testPbkdf2PasswordHashUniqueness(ValidPassword data) returns e string hash2 = check hashPbkdf2(data.password); string hash3 = check hashPbkdf2(data.password); - test:assertNotEquals(hash1, hash2, "Hashes should be unique for: " + data.password); - test:assertNotEquals(hash2, hash3, "Hashes should be unique for: " + data.password); - test:assertNotEquals(hash1, hash3, "Hashes should be unique for: " + data.password); + test:assertNotEquals(hash1, hash2, string `Hashes should be unique for: ${data.password}`); + test:assertNotEquals(hash2, hash3, string `Hashes should be unique for: ${data.password}`); + test:assertNotEquals(hash1, hash3, string `Hashes should be unique for: ${data.password}`); boolean verify1 = check verifyPbkdf2(data.password, hash1); boolean verify2 = check verifyPbkdf2(data.password, hash2); boolean verify3 = check verifyPbkdf2(data.password, hash3); test:assertTrue(verify1 && verify2 && verify3, - "All hashes should verify successfully for: " + data.password); + string `All hashes should verify successfully for: ${data.password}`); } @test:Config { @@ -437,11 +436,11 @@ isolated function testPbkdf2DifferentAlgorithms(HmacAlgorithm algorithm) returns string password = "Ballerina@123"; string hash = check hashPbkdf2(password, 10000, algorithm); - test:assertTrue(hash.startsWith("$pbkdf2-" + algorithm.toString().toLowerAscii() + "$"), - "Hash should start with correct algorithm identifier"); + test:assertTrue(hash.startsWith(string `$pbkdf2-${algorithm.toString().toLowerAscii()}$`), + string `Hash should start with correct algorithm identifier: ${algorithm.toString()}`); boolean result = check verifyPbkdf2(password, hash); - test:assertTrue(result, "Password verification failed for algorithm: " + algorithm.toString()); + test:assertTrue(result, string `Password verification failed for algorithm: ${algorithm.toString()}`); } // data Providers for password tests @@ -555,6 +554,7 @@ isolated function invalidPbkdf2HashesDataProvider() returns InvalidHash[][] { [{hash: "$pbkdf2-md5$i=10000$salt$hash", expectedError: "Error occurred while verifying password: Unsupported algorithm: MD5"}] ]; } + isolated function pbkdf2AlgorithmsDataProvider() returns HmacAlgorithm[][] { return [ [SHA1], @@ -562,4 +562,3 @@ isolated function pbkdf2AlgorithmsDataProvider() returns HmacAlgorithm[][] { [SHA512] ]; } - From ce4da8e14ffc5b97f68c517449f650114057fdf0 Mon Sep 17 00:00:00 2001 From: Randil Date: Thu, 3 Apr 2025 22:37:24 +0530 Subject: [PATCH 21/26] Add password hashing constants and refactor formatting methods for BCrypt, Argon2, and PBKDF2 --- .../java/io/ballerina/stdlib/crypto/Constants.java | 9 +++++++++ .../java/io/ballerina/stdlib/crypto/PasswordUtils.java | 10 +++++----- .../ballerina/stdlib/crypto/nativeimpl/Password.java | 3 ++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java index 8c432e7b..24c12181 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/Constants.java @@ -143,4 +143,13 @@ private Constants() {} public static final String SHA384 = "SHA-384"; public static final String SHA512 = "SHA-512"; public static final String KECCAK256 = "Keccak-256"; + + // Password hashing constants + public static final String BCRYPT_HASH_FORMAT = "$2a$%02d$%s"; + public static final String ARGON2_SALT_FORMAT = "$argon2id$v=19$m=%d,t=%d,p=%d$%s"; + public static final String ARGON2_HASH_FORMAT = "$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s"; + public static final String PBKDF2_HASH_PATTERN = + "\\$pbkdf2-(\\w+)\\$i=(\\d+)\\$([A-Za-z0-9+/=]+)\\$([A-Za-z0-9+/=]+)"; + public static final String PBKDF2_SALT_FORMAT = "$pbkdf2-%s$i=%d$%s"; + public static final String PBKDF2_HASH_FORMAT = "$pbkdf2-%s$i=%d$%s$%s"; } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java index b6e8f2f6..bbe72deb 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java @@ -173,7 +173,7 @@ public static byte[] generateRandomSalt() { */ public static String formatBCryptHash(long workFactor, byte[] saltAndHash) { String saltAndHashBase64 = Base64.toBase64String(saltAndHash); - return String.format(Locale.ROOT, "$2a$%02d$%s", workFactor, saltAndHashBase64); + return String.format(Locale.ROOT, Constants.BCRYPT_HASH_FORMAT, workFactor, saltAndHashBase64); } /** @@ -188,7 +188,7 @@ public static String formatBCryptHash(long workFactor, byte[] saltAndHash) { */ public static String formatArgon2Hash(long memory, long iterations, long parallelism, String saltBase64, String hashBase64) { - return String.format(Locale.ROOT, "$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s", + return String.format(Locale.ROOT, Constants.ARGON2_HASH_FORMAT, memory, iterations, parallelism, saltBase64, hashBase64); } @@ -202,7 +202,7 @@ public static String formatArgon2Hash(long memory, long iterations, long paralle * @return formatted Argon2 salt string */ public static String formatArgon2Salt(long memory, long iterations, long parallelism, String saltBase64) { - return String.format(Locale.ROOT, "$argon2id$v=19$m=%d,t=%d,p=%d$%s", + return String.format(Locale.ROOT, Constants.ARGON2_SALT_FORMAT, memory, iterations, parallelism, saltBase64); } @@ -217,7 +217,7 @@ public static String formatArgon2Salt(long memory, long iterations, long paralle */ public static String formatPBKDF2Hash(String algorithm, long iterations, String saltBase64, String hashBase64) { - return String.format(Locale.ROOT, "$pbkdf2-%s$i=%d$%s$%s", + return String.format(Locale.ROOT, Constants.PBKDF2_HASH_FORMAT, algorithm.toLowerCase(Locale.ROOT), iterations, saltBase64, hashBase64); } @@ -230,7 +230,7 @@ public static String formatPBKDF2Hash(String algorithm, long iterations, * @return formatted PBKDF2 salt string */ public static String formatPBKDF2Salt(String algorithm, long iterations, String saltBase64) { - return String.format(Locale.ROOT, "$pbkdf2-%s$i=%d$%s", + return String.format(Locale.ROOT, Constants.PBKDF2_SALT_FORMAT, algorithm.toLowerCase(Locale.ROOT), iterations, saltBase64); } diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java index 509df04a..7a041653 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java @@ -19,6 +19,7 @@ import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.crypto.Constants; import io.ballerina.stdlib.crypto.CryptoUtils; import io.ballerina.stdlib.crypto.PasswordUtils; import org.bouncycastle.crypto.generators.Argon2BytesGenerator; @@ -357,7 +358,7 @@ public static Object hashPasswordPBKDF2(BString password, long iterations, BStri public static Object verifyPasswordPBKDF2(BString password, BString hashedPassword) { try { String hash = hashedPassword.getValue(); - Pattern pattern = Pattern.compile("\\$pbkdf2-(\\w+)\\$i=(\\d+)\\$([A-Za-z0-9+/=]+)\\$([A-Za-z0-9+/=]+)"); + Pattern pattern = Pattern.compile(Constants.PBKDF2_HASH_PATTERN); Matcher matcher = pattern.matcher(hash); if (!matcher.matches()) { From 0e29066def9836bd50f1d2e0c93f0e2a31bf98e3 Mon Sep 17 00:00:00 2001 From: Randil Tharusha <51049280+randilt@users.noreply.github.com> Date: Fri, 4 Apr 2025 13:01:49 +0530 Subject: [PATCH 22/26] Update ballerina/hash.bal Co-authored-by: MohamedSabthar --- ballerina/hash.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/hash.bal b/ballerina/hash.bal index 099f0fa1..3e7ddace 100644 --- a/ballerina/hash.bal +++ b/ballerina/hash.bal @@ -215,4 +215,4 @@ public isolated function hashPbkdf2(string password, int iterations = 10000, Hma public isolated function verifyPbkdf2(string password, string hashedPassword) returns boolean|Error = @java:Method { name: "verifyPasswordPBKDF2", 'class: "io.ballerina.stdlib.crypto.nativeimpl.Password" -} external; \ No newline at end of file +} external; From 19b52602e6cb302fc01c12b3872d9bfb16431c6a Mon Sep 17 00:00:00 2001 From: Randil Date: Fri, 4 Apr 2025 22:21:50 +0530 Subject: [PATCH 23/26] Introduce HmacAlgorithm enum for PBKDF2 hashing API --- docs/proposals/pbkdf2-hashing-apis.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/proposals/pbkdf2-hashing-apis.md b/docs/proposals/pbkdf2-hashing-apis.md index cfcebf8d..e703cd5b 100644 --- a/docs/proposals/pbkdf2-hashing-apis.md +++ b/docs/proposals/pbkdf2-hashing-apis.md @@ -31,10 +31,16 @@ This proposal aims to introduce secure PBKDF2 password hashing and verification A new API will be introduced to generate PBKDF2 hashes with configurable parameters: ```ballerina +public enum HmacAlgorithm { + SHA1, + SHA256, + SHA512 +} + public isolated function hashPbkdf2( string password, int iterations = 10000, - "SHA1"|"SHA256"|"SHA512" algorithm = "SHA256" + HmacAlgorithm algorithm = SHA256 ) returns string|Error; ``` From 1d832b10e89266145387e7dfd4a82deb6a2e8390 Mon Sep 17 00:00:00 2001 From: Randil Date: Fri, 4 Apr 2025 22:25:59 +0530 Subject: [PATCH 24/26] Update the spec for Pbkdf2 APIs --- docs/spec/spec.md | 231 +++++++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 105 deletions(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 5fbc7bc8..66570c50 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -4,7 +4,7 @@ _Owners_: @shafreenAnfar @bhashinee _Reviewers_: @shafreenAnfar _Created_: 2022/08/23 _Updated_: 2025/01/20 -_Edition_: Swan Lake +_Edition_: Swan Lake ## Introduction @@ -17,97 +17,97 @@ If you have any feedback or suggestions about the library, start a discussion vi The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug. ## Contents + 1. [Overview](#1-overview) 2. [Hash](#2-hash) - * 2.1. [MD5](#21-md5) - * 2.2. [SHA1](#22-sha1) - * 2.3. [SHA256](#23-sha256) - * 2.4. [SHA384](#24-sha384) - * 2.5. [SHA512](#25-sha512) - * 2.6. [CRC32B](#26-crc32b) - * 2.7. [KECCAK256](#27-keccak256) + - 2.1. [MD5](#21-md5) + - 2.2. [SHA1](#22-sha1) + - 2.3. [SHA256](#23-sha256) + - 2.4. [SHA384](#24-sha384) + - 2.5. [SHA512](#25-sha512) + - 2.6. [CRC32B](#26-crc32b) + - 2.7. [KECCAK256](#27-keccak256) 3. [HMAC](#3-hmac) - * 3.1. [MD5](#31-md5) - * 3.2. [SHA1](#32-sha1) - * 3.3. [SHA256](#33-sha256) - * 3.4. [SHA384](#34-sha384) - * 3.5. [SHA512](#35-sha512) + - 3.1. [MD5](#31-md5) + - 3.2. [SHA1](#32-sha1) + - 3.3. [SHA256](#33-sha256) + - 3.4. [SHA384](#34-sha384) + - 3.5. [SHA512](#35-sha512) 4. [Decode private/public key](#4-decode-private-public-keys) - * 4.1. [Decode RSA Private key from PKCS12 file](#41-rsa-decode-private-key-from-pkcs12-file) - * 4.2. [Decode RSA Private key using Private key and Password](#42-decode-rsa-private-key-using-private-key-and-password) - * 4.3. [Decode RSA Private key using Private key content and Password](#43-decode-rsa-private-key-using-private-key-content-and-password) - * 4.4. [Decode RSA Public key from PKCS12 file](#44-decode-rsa-public-key-from-pkcs12-file) - * 4.5. [Decode RSA Public key from the certificate file](#45-decode-rsa-public-key-from-the-certificate-file) - * 4.6. [Decode RSA Public key from the certificate content](#46-decode-rsa-public-key-from-the-certificate-content) - * 4.7. [Decode EC Private key from PKCS12 file](#47-decode-ec-private-key-from-pkcs12-file) - * 4.8. [Decode EC Private key using Private key and Password](#48-decode-ec-private-key-using-private-key-and-password) - * 4.9. [Decode EC Public key from PKCS12 file](#49-decode-ec-public-key-from-pkcs12-file) - * 4.10. [Decode EC Public key from the certificate file](#410-decode-ec-public-key-from-the-certificate-file) - * 4.11. [Build RSA Public key from modulus and exponent parameters](#411-build-rsa-public-key-from-modulus-and-exponent-parameters) - * 4.12. [Decode ML-DSA-65 Private key from PKCS12 file](#412-decode-ml-dsa-65-private-key-from-pkcs12-file) - * 4.13. [Decode ML-DSA-65 Private key using Private key and Password](#413-decode-ml-dsa-65-private-key-using-private-key-and-password) - * 4.14. [Decode ML-DSA-65 Public key from PKCS12 file](#414-decode-ml-dsa-65-public-key-from-pkcs12-file) - * 4.15. [Decode ML-DSA-65 Public key from the certificate file](#415-decode-ml-dsa-65-public-key-from-the-certificate-file) - * 4.16. [Decode ML-KEM-768 Private key from PKCS12 file](#416-decode-ml-kem-768-private-key-from-pkcs12-file) - * 4.17. [Decode ML-KEM-768 Private key using Private key and Password](#417-decode-ml-kem-768-private-key-using-private-key-and-password) - * 4.18. [Decode ML-KEM-768 Public key from PKCS12 file](#418-decode-ml-kem-768-public-key-from-pkcs12-file) - * 4.19. [Decode ML-KEM-768 Public key from the certificate file](#419-decode-ml-kem-768-public-key-from-the-certificate-file) -5. [Encrypt-Decrypt](#5-encrypt-decrypt) - * 5.1. [Encryption](#51-encryption) - * 5.1.1. [RSA](#511-rsa) - * 5.1.2. [AES-CBC](#512-aes-cbc) - * 5.1.3. [AES-ECB](#513-aes-ecb) - * 5.1.4. [AES-GCM](#514-aes-gcm) - * 5.1.5. [PGP](#515-pgp) - * 5.2. [Decryption](#52-decryption) - * 5.2.1. [RSA-ECB](#521-rsa-ecb) - * 5.2.2. [AES-CBC](#522-aes-cbc) - * 5.2.3. [AES-ECB](#523-aes-ecb) - * 5.2.4. [AES-GCM](#524-aes-gcm) - * 5.2.5. [PGP](#525-pgp) + - 4.1. [Decode RSA Private key from PKCS12 file](#41-rsa-decode-private-key-from-pkcs12-file) + - 4.2. [Decode RSA Private key using Private key and Password](#42-decode-rsa-private-key-using-private-key-and-password) + - 4.3. [Decode RSA Private key using Private key content and Password](#43-decode-rsa-private-key-using-private-key-content-and-password) + - 4.4. [Decode RSA Public key from PKCS12 file](#44-decode-rsa-public-key-from-pkcs12-file) + - 4.5. [Decode RSA Public key from the certificate file](#45-decode-rsa-public-key-from-the-certificate-file) + - 4.6. [Decode RSA Public key from the certificate content](#46-decode-rsa-public-key-from-the-certificate-content) + - 4.7. [Decode EC Private key from PKCS12 file](#47-decode-ec-private-key-from-pkcs12-file) + - 4.8. [Decode EC Private key using Private key and Password](#48-decode-ec-private-key-using-private-key-and-password) + - 4.9. [Decode EC Public key from PKCS12 file](#49-decode-ec-public-key-from-pkcs12-file) + - 4.10. [Decode EC Public key from the certificate file](#410-decode-ec-public-key-from-the-certificate-file) + - 4.11. [Build RSA Public key from modulus and exponent parameters](#411-build-rsa-public-key-from-modulus-and-exponent-parameters) + - 4.12. [Decode ML-DSA-65 Private key from PKCS12 file](#412-decode-ml-dsa-65-private-key-from-pkcs12-file) + - 4.13. [Decode ML-DSA-65 Private key using Private key and Password](#413-decode-ml-dsa-65-private-key-using-private-key-and-password) + - 4.14. [Decode ML-DSA-65 Public key from PKCS12 file](#414-decode-ml-dsa-65-public-key-from-pkcs12-file) + - 4.15. [Decode ML-DSA-65 Public key from the certificate file](#415-decode-ml-dsa-65-public-key-from-the-certificate-file) + - 4.16. [Decode ML-KEM-768 Private key from PKCS12 file](#416-decode-ml-kem-768-private-key-from-pkcs12-file) + - 4.17. [Decode ML-KEM-768 Private key using Private key and Password](#417-decode-ml-kem-768-private-key-using-private-key-and-password) + - 4.18. [Decode ML-KEM-768 Public key from PKCS12 file](#418-decode-ml-kem-768-public-key-from-pkcs12-file) + - 4.19. [Decode ML-KEM-768 Public key from the certificate file](#419-decode-ml-kem-768-public-key-from-the-certificate-file) +5. [Encrypt-Decrypt](#5-encrypt-decrypt) + - 5.1. [Encryption](#51-encryption) + - 5.1.1. [RSA](#511-rsa) + - 5.1.2. [AES-CBC](#512-aes-cbc) + - 5.1.3. [AES-ECB](#513-aes-ecb) + - 5.1.4. [AES-GCM](#514-aes-gcm) + - 5.1.5. [PGP](#515-pgp) + - 5.2. [Decryption](#52-decryption) + - 5.2.1. [RSA-ECB](#521-rsa-ecb) + - 5.2.2. [AES-CBC](#522-aes-cbc) + - 5.2.3. [AES-ECB](#523-aes-ecb) + - 5.2.4. [AES-GCM](#524-aes-gcm) + - 5.2.5. [PGP](#525-pgp) 6. [Sign and Verify](#6-sign-and-verify) - * 6.1. [Sign messages](#61-sign-messages) - * 6.1.1. [RSA-MD5](#611-rsa-md5) - * 6.1.2. [RSA-SHA1](#612-rsa-sha1) - * 6.1.3. [RSA-SHA256](#613-rsa-sha256) - * 6.1.4. [RSA-SHA384](#614-rsa-sha384) - * 6.1.5. [RSA-SHA512](#615-rsa-sha512) - * 6.1.6. [SHA384withECDSA](#616-sha384withecdsa) - * 6.1.7. [SHA256withECDSA](#617-sha256withecdsa) - * 6.1.8. [ML-DSA-65](#618-mldsa65) - * 6.2. [Verify signature](#62-verify-signature) - * 6.2.1. [RSA-MD5](#621-rsa-md5) - * 6.2.2. [RSA-SHA1](#622-rsa-sha1) - * 6.2.3. [RSA-SHA256](#623-rsa-sha256) - * 6.2.4. [RSA-SHA384](#624-rsa-sha384) - * 6.2.5. [RSA-SHA512](#625-rsa-sha512) - * 6.2.6. [SHA384withECDSA](#626-sha384withecdsa) - * 6.2.7. [SHA256withECDSA](#627-sha256withecdsa) - * 6.2.8. [ML-DSA-65](#618-mldsa65) + - 6.1. [Sign messages](#61-sign-messages) + - 6.1.1. [RSA-MD5](#611-rsa-md5) + - 6.1.2. [RSA-SHA1](#612-rsa-sha1) + - 6.1.3. [RSA-SHA256](#613-rsa-sha256) + - 6.1.4. [RSA-SHA384](#614-rsa-sha384) + - 6.1.5. [RSA-SHA512](#615-rsa-sha512) + - 6.1.6. [SHA384withECDSA](#616-sha384withecdsa) + - 6.1.7. [SHA256withECDSA](#617-sha256withecdsa) + - 6.1.8. [ML-DSA-65](#618-mldsa65) + - 6.2. [Verify signature](#62-verify-signature) + - 6.2.1. [RSA-MD5](#621-rsa-md5) + - 6.2.2. [RSA-SHA1](#622-rsa-sha1) + - 6.2.3. [RSA-SHA256](#623-rsa-sha256) + - 6.2.4. [RSA-SHA384](#624-rsa-sha384) + - 6.2.5. [RSA-SHA512](#625-rsa-sha512) + - 6.2.6. [SHA384withECDSA](#626-sha384withecdsa) + - 6.2.7. [SHA256withECDSA](#627-sha256withecdsa) + - 6.2.8. [ML-DSA-65](#618-mldsa65) 7. [Key Derivation Function (KDF)](#7-key-derivation-function-kdf) - * 7.1. [HKDF-SHA256](#71-hkdf-sha256) + - 7.1. [HKDF-SHA256](#71-hkdf-sha256) 8. [Key Exchange Mechanism (KEM)](#8-key-exchange-mechanism-kem) - * 8.1 [Encapsulation](#81-encapsulation) - * 8.1.1 [RSA-KEM](#811-rsa-kem) - * 8.1.2 [ML-KEM-768](#812-ml-kem-768) - * 8.1.3 [RSA-KEM-ML-KEM-768](#813-rsa-kem-ml-kem-768) - * 8.2 [Decapsulation](#81-encapsulation) - * 8.2.1 [RSA-KEM](#821-rsa-kem) - * 8.2.2 [ML-KEM-768](#822-ml-kem-768) - * 8.2.3 [RSA-KEM-ML-KEM-768](#823-rsa-kem-ml-kem-768) + - 8.1 [Encapsulation](#81-encapsulation) + - 8.1.1 [RSA-KEM](#811-rsa-kem) + - 8.1.2 [ML-KEM-768](#812-ml-kem-768) + - 8.1.3 [RSA-KEM-ML-KEM-768](#813-rsa-kem-ml-kem-768) + - 8.2 [Decapsulation](#81-encapsulation) + - 8.2.1 [RSA-KEM](#821-rsa-kem) + - 8.2.2 [ML-KEM-768](#822-ml-kem-768) + - 8.2.3 [RSA-KEM-ML-KEM-768](#823-rsa-kem-ml-kem-768) 9. [Hybrid Public Key Encryption (HPKE)](#9-hybrid-public-key-encryption-hpke) - * 9.1 [Encrypt](#91-encrypt) - * 9.1.1 [ML-KEM-768-HPKE](#911-ml-kem-768-hpke) - * 9.1.2 [RSA-KEM-ML-KEM-768-HPKE](#912-rsa-kem-ml-kem-768-hpke) - * 9.2 [Decrypt](#92-decrypt) - * 9.2.1 [ML-KEM-768-HPKE](#921-ml-kem-768-hpke) - * 9.2.2 [RSA-KEM-ML-KEM-768-HPKE](#922-rsa-kem-ml-kem-768-hpke) + - 9.1 [Encrypt](#91-encrypt) + - 9.1.1 [ML-KEM-768-HPKE](#911-ml-kem-768-hpke) + - 9.1.2 [RSA-KEM-ML-KEM-768-HPKE](#912-rsa-kem-ml-kem-768-hpke) + - 9.2 [Decrypt](#92-decrypt) + - 9.2.1 [ML-KEM-768-HPKE](#921-ml-kem-768-hpke) + - 9.2.2 [RSA-KEM-ML-KEM-768-HPKE](#922-rsa-kem-ml-kem-768-hpke) 10. [Password hashing](#10-password-hashing) - * 10.1 [BCrypt](#101-bcrypt) - * 10.2 [Argon2](#102-argon2) - * 10.3 [PBKDF2](#103-pbkdf2) + - 10.1 [BCrypt](#101-bcrypt) + - 10.2 [Argon2](#102-argon2) + - 10.3 [PBKDF2](#103-pbkdf2) - ## 1. [Overview](#1-overview) The Ballerina `crypto` library facilitates APIs to do operations like hashing, HMAC generation, checksum generation, encryption, decryption, signing data digitally, verifying digitally signed data, etc., with different cryptographic algorithms. @@ -119,6 +119,7 @@ The `crypto` library supports generating hashes with 5 different hash algorithms ### 2.1. [MD5](#21-md) This API can be used to create the MD5 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -128,6 +129,7 @@ byte[] hash = crypto:hashMd5(data); ### 2.2. [SHA1](#22-sha1) This API can be used to create the SHA-1 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -137,6 +139,7 @@ byte[] hash = crypto:hashSha1(data); ### 2.3. [SHA256](#23-sha256) This API can be used to create the SHA-256 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -146,6 +149,7 @@ byte[] hash = crypto:hashSha256(data); ### 2.4. [SHA384](#24-sha384) This API can be used to create the SHA-384 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -155,6 +159,7 @@ byte[] hash = crypto:hashSha384(data); ### 2.5. [SHA512](#25-sha512) This API can be used to create the SHA-512 hash of the given data. + ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -164,6 +169,7 @@ byte[] hash = crypto:hashSha512(data); ### 2.6. [CRC32B](#26-crc32b) This API can be used to create the Hex-encoded CRC32B value of the given data. + ```ballerina string stringData = "Hello Ballerina"; byte[] data = stringData.toBytes(); @@ -173,6 +179,7 @@ string checksum = crypto:crc32b(data); ### 2.7. [KECCAK256](#27-keccak256) This API can be used to create the Hex-encoded KECCAK-256 value of the given data. + ```ballerina string stringData = "Hello Ballerina"; byte[] data = stringData.toBytes(); @@ -446,7 +453,7 @@ string certFile = "/path/to/public.cert"; crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromCertFile(certFile); ``` -## 5. [Encrypt-Decrypt](#5-encrypt-decrypt) +## 5. [Encrypt-Decrypt](#5-encrypt-decrypt) The `crypto` library supports both symmetric key encryption/decryption and asymmetric key encryption/decryption. The RSA algorithm can be used for asymmetric-key encryption/decryption with the use of private and public keys. The AES algorithm can be used for symmetric-key encryption/decryption with the use of a shared key. @@ -532,7 +539,7 @@ byte[] cipherText = check crypto:encryptPgp(data, publicKeyPath); The following encryption options can be configured in the PGP encryption. | Option | Description | Default Value | -|-----------------------|-------------------------------------------------------------------|---------------| +| --------------------- | ----------------------------------------------------------------- | ------------- | | compressionAlgorithm | Specifies the compression algorithm used for PGP encryption | ZIP | | symmetricKeyAlgorithm | Specifies the symmetric key algorithm used for encryption | AES_256 | | armor | Indicates whether ASCII armor is enabled for the encrypted output | true | @@ -913,7 +920,6 @@ crypto:PublicKey publicKey = check crypto:decodeMlDsa65PublicKeyFromTrustStore(k boolean validity = check crypto:verifyMlDsa65Signature(data, signature, publicKey); ``` - ## 7. [Key Derivation Function (KDF)](#7-key-derivation-function-kdf) The `crypto` module supports HMAC-based Key Derivation Function (HKDF). HKDF is a key derivation function that uses a Hash-based Message Authentication Code (HMAC) to derive keys. @@ -933,10 +939,10 @@ byte[] hash = crypto:hkdfSha256(key, 32); The `crypto` module supports Key Exchange Mechanisms (KEM). It includes RSA-KEM and post-quantum ML-KEM-768 for both encapsulation and decapsulation. ### 8.1. [Encapsulation](#81-encapsulation) - + #### 8.1.1. [RSA-KEM](#811-rsa-kem) -This API can be used to create shared secret and its encapsulation using RSA-KEM function. +This API can be used to create shared secret and its encapsulation using RSA-KEM function. ```ballerina crypto:KeyStore keyStore = { @@ -946,10 +952,10 @@ crypto:KeyStore keyStore = { crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, "keyAlias"); crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateRsaKem(publicKey); ``` - + #### 8.1.2. [ML-KEM-768](#812-ml-kem-768) -This API can be used to create shared secret and its encapsulation using ML-KEM-768 function. +This API can be used to create shared secret and its encapsulation using ML-KEM-768 function. ```ballerina crypto:KeyStore keyStore = { @@ -959,10 +965,10 @@ crypto:KeyStore keyStore = { crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias"); crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateMlKem768(publicKey); ``` - + #### 8.1.3. [RSA-KEM-ML-KEM-768](#813-rsa-kem-ml-kem-768) -This API can be used to create shared secret and its encapsulation using RSA-KEM-ML-KEM-768 function. +This API can be used to create shared secret and its encapsulation using RSA-KEM-ML-KEM-768 function. ```ballerina crypto:KeyStore mlkemKeyStore = { @@ -983,10 +989,10 @@ byte[] sharedSecret = check crypto:decapsulateRsaKemMlKem768(encapsulatedSecret, ``` ### 8.2. [Decapsulation](#81-encapsulation) - + #### 8.2.1. [RSA-KEM](#821-rsa-kem) -This API can be used to decapsulate shared secret using RSA-KEM function of the given data. +This API can be used to decapsulate shared secret using RSA-KEM function of the given data. ```ballerina crypto:KeyStore keyStore = { @@ -999,10 +1005,10 @@ byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret; crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyStorePassword"); byte[] sharedSecret = check crypto:decapsulateRsaKem(encapsulatedSecret, privateKey); ``` - + #### 8.2.2. [ML-KEM-768](#822-ml-kem-768) -This API can be used to decapsulate shared secret using ML-KEM-768 function of the given data. +This API can be used to decapsulate shared secret using ML-KEM-768 function of the given data. ```ballerina crypto:KeyStore keyStore = { @@ -1015,10 +1021,10 @@ byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret; crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(keyStore, "keyAlias", "keyStorePassword"); byte[] sharedSecret = check crypto:decapsulateMlKem768(encapsulatedSecret, privateKey); ``` - + #### 8.2.3. [RSA-KEM-ML-KEM-768](#823-rsa-kem-ml-kem-768) -This API can be used to decapsulate shared secret using RSA-KEM-ML-KEM-768 function of the given data. +This API can be used to decapsulate shared secret using RSA-KEM-ML-KEM-768 function of the given data. ```ballerina crypto:KeyStore mlkemKeyStore = { @@ -1043,7 +1049,7 @@ byte[] sharedSecret = check crypto:decapsulateRsaKemMlKem768(encapsulatedSecret, The `crypto` module supports Hybrid Public Key Encryption (HPKE). It supports post-quantum ML-KEM-768-HPKE and RSA-KEM-ML-KEM-768-HPKE for encryption and decryption. ### 9.1. [Encrypt](#91-encrypt) - + #### 9.1.1. [ML-KEM-768-HPKE](#911-ml-kem-768-hpke) This API can be used to create the ML-KEM-768-hybrid-encrypted value of the given data. @@ -1058,7 +1064,7 @@ crypto:KeyStore keyStore = { crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias"); crypto:HybridEncryptionResult encryptionResult = check crypto:encryptMlKem768Hpke(data, publicKey); ``` - + #### 9.1.2. [RSA-KEM-ML-KEM-768-HPKE](#912-rsa-kem-ml-kem-768-hpke) This API can be used to create the RSA-KEM-ML-KEM-768-hybrid-encrypted value of the given data. @@ -1080,7 +1086,7 @@ crypto:HybridEncryptionResult encryptionResult = check crypto:encryptRsaKemMlKem ``` ### 9.2. [Decrypt](#92-decrypt) - + #### 9.2.1. [ML-KEM-768-HPKE](#921-ml-kem-768-hpke) This API can be used to create the ML-KEM-768-hybrid-decrypted value of the given data. @@ -1099,7 +1105,7 @@ byte[] encapsulatedKey = encryptionResult.encapsulatedSecret; crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(keyStore, "keyAlias", "keyStorePassword"); byte[] decryptedData = check crypto:decryptMlKem768Hpke(cipherText, encapsulatedKey, privateKey); ``` - + #### 9.2.2. [RSA-KEM-ML-KEM-768-HPKE](#922-rsa-kem-ml-kem-768-hpke) This API can be used to create the RSA-KEM-ML-KEM-768-hybrid-decrypted value of the given data. @@ -1138,6 +1144,7 @@ public isolated function hashBcrypt(string password, int workFactor = 12) return ``` Parameters: + - `password`: The plain text password to hash - `workFactor`: Computational complexity factor (4-31, default: 12) @@ -1146,6 +1153,7 @@ public isolated function verifyBcrypt(string password, string hashedPassword) re ``` Example: + ```ballerina string password = "your-password"; // Hash with default work factor (12) @@ -1160,11 +1168,12 @@ boolean isValid = check crypto:verifyBcrypt(password, hashedPassword1); Implements the Argon2id variant of the Argon2 password hashing algorithm, optimized for both high memory usage and GPU resistance. ```ballerina -public isolated function hashArgon2(string password, int iterations = 3, +public isolated function hashArgon2(string password, int iterations = 3, int memory = 65536, int parallelism = 4) returns string|Error ``` Parameters: + - `password`: The plain text password to hash - `iterations`: Number of iterations (default: 3) - `memory`: Memory usage in KB (minimum: 8192, default: 65536) @@ -1177,6 +1186,7 @@ public isolated function verifyArgon2(string password, string hashedPassword) re ``` Example: + ```ballerina string password = "your-password"; // Hash with default parameters @@ -1185,27 +1195,36 @@ string hashedPassword1 = check crypto:hashArgon2(password); string hashedPassword2 = check crypto:hashArgon2(password, iterations = 4, memory = 131072, parallelism = 8); boolean isValid = check crypto:verifyArgon2(password, hashedPassword1); ``` + ### 10.3 [PBKDF2](#103-pbkdf2) Implements the PBKDF2 (Password-Based Key Derivation Function 2) algorithm for password hashing. ```ballerina -public isolated function hashPbkdf2(string password, int iterations = 10000, - string algorithm = "SHA256") returns string|Error +public enum HmacAlgorithm { + SHA1, + SHA256, + SHA512 +} + +public isolated function hashPbkdf2(string password, int iterations = 10000, + HmacAlgorithm algorithm = SHA256) returns string|Error ``` Parameters: + - `password`: The plain text password to hash - `iterations`: Optional number of iterations (default: 10000) - `algorithm`: Optional HMAC algorithm (SHA1, SHA256, SHA512). Default is SHA256 Example: + ```ballerina string password = "mySecurePassword123"; // Hash with default parameters string hashedPassword = check crypto:hashPbkdf2(password); // Hash with custom parameters -string customHashedPassword = check crypto:hashPbkdf2(password, iterations = 15000, algorithm = "SHA512"); +string customHashedPassword = check crypto:hashPbkdf2(password, iterations = 15000, algorithm = SHA512); ``` ```ballerina @@ -1213,10 +1232,12 @@ public isolated function verifyPbkdf2(string password, string hashedPassword) re ``` Parameters: + - `password`: The plain text password to verify - `hashedPassword`: PBKDF2 hashed password to verify against Example: + ```ballerina string password = "mySecurePassword123"; string hashedPassword = "$pbkdf2-sha256$i=10000$salt$hash"; From 202936936b5d64a802c1a925e92d2db369e61795 Mon Sep 17 00:00:00 2001 From: Randil Date: Fri, 11 Apr 2025 19:55:14 +0530 Subject: [PATCH 25/26] Remove default parameters for BCrypt, Argon2, and PBKDF2 in PasswordUtils and Password classes --- .../stdlib/crypto/PasswordUtils.java | 37 +---------- .../stdlib/crypto/nativeimpl/Password.java | 65 +------------------ 2 files changed, 3 insertions(+), 99 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java index bbe72deb..8a006275 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/PasswordUtils.java @@ -38,55 +38,20 @@ public class PasswordUtils { */ public static final int MAX_WORK_FACTOR = 31; - /** - * Default work factor used for BCrypt password hashing if not specified. - */ - public static final int DEFAULT_WORK_FACTOR = 12; - /** * Length of the random salt used in password hashing. */ public static final int SALT_LENGTH = 16; - /** - * Default number of iterations for Argon2. - */ - public static final int DEFAULT_ITERATIONS = 3; - - /** - * Default memory usage in KB (64MB) for Argon2. - */ - public static final int DEFAULT_MEMORY = 65536; - - /** - * Default number of parallel threads for Argon2. - */ - public static final int DEFAULT_PARALLELISM = 4; - /** * Length of the generated hash in bytes for Argon2. */ public static final int HASH_LENGTH = 32; - - /** - * Default number of iterations for PBKDF2. - */ - public static final int DEFAULT_PBKDF2_ITERATIONS = 10000; - + /** * Minimum number of iterations for PBKDF2. */ public static final int MIN_PBKDF2_ITERATIONS = 10000; - - /** - * Length of the generated hash in bytes for PBKDF2. - */ - public static final int PBKDF2_HASH_LENGTH = 32; - - /** - * Default HMAC algorithm for PBKDF2. - */ - public static final String DEFAULT_PBKDF2_ALGORITHM = "SHA256"; /** * Minimum allowed memory cost for PBKDF2. diff --git a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java index 7a041653..93e8f6b1 100644 --- a/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java +++ b/native/src/main/java/io/ballerina/stdlib/crypto/nativeimpl/Password.java @@ -77,16 +77,6 @@ public static Object hashPassword(BString password, long workFactor) { } } - /** - * Hash a password using BCrypt with the default work factor. - * - * @param password the password to hash - * @return hashed password string or error - */ - public static Object hashPassword(BString password) { - return hashPassword(password, PasswordUtils.DEFAULT_WORK_FACTOR); - } - /** * Verify a password against a BCrypt hash. * @@ -142,28 +132,8 @@ public static Object generateSalt(long workFactor) { } } - /** - * Generate a salt string for BCrypt with the default work factor. - * - * @return formatted salt string or error - */ - public static Object generateSalt() { - return generateSalt(PasswordUtils.DEFAULT_WORK_FACTOR); - } - // Argon2 methods - /** - * Hash a password using Argon2 with default parameters. - * - * @param password the password to hash - * @return hashed password string or error - */ - public static Object hashPasswordArgon2(BString password) { - return hashPasswordArgon2(password, PasswordUtils.DEFAULT_ITERATIONS, PasswordUtils.DEFAULT_MEMORY, - PasswordUtils.DEFAULT_PARALLELISM); - } - /** * Hash a password using Argon2 with custom parameters. * @@ -259,16 +229,6 @@ public static Object verifyPasswordArgon2(BString password, BString hashedPasswo } } - /** - * Generate a salt string for Argon2 with default parameters. - * - * @return formatted salt string or error - */ - public static Object generateSaltArgon2() { - return generateSaltArgon2(PasswordUtils.DEFAULT_ITERATIONS, PasswordUtils.DEFAULT_MEMORY, - PasswordUtils.DEFAULT_PARALLELISM); - } - /** * Generate a salt string for Argon2 with custom parameters. * @@ -291,18 +251,7 @@ public static Object generateSaltArgon2(long iterations, long memory, long paral } // PBKDF2 methods - - /** - * Hash a password using PBKDF2 with default parameters. - * - * @param password the password to hash - * @return hashed password string or error - */ - public static Object hashPasswordPBKDF2(BString password) { - return hashPasswordPBKDF2(password, PasswordUtils.DEFAULT_PBKDF2_ITERATIONS, - StringUtils.fromString(PasswordUtils.DEFAULT_PBKDF2_ALGORITHM)); - } - + /** * Hash a password using PBKDF2 with custom parameters. * @@ -383,17 +332,7 @@ public static Object verifyPasswordPBKDF2(BString password, BString hashedPasswo return CryptoUtils.createError("Error occurred while verifying password: " + e.getMessage()); } } - - /** - * Generate a salt string for PBKDF2 with default parameters. - * - * @return formatted salt string or error - */ - public static Object generateSaltPBKDF2() { - return generateSaltPBKDF2(PasswordUtils.DEFAULT_PBKDF2_ITERATIONS, - StringUtils.fromString(PasswordUtils.DEFAULT_PBKDF2_ALGORITHM)); - } - + /** * Generate a salt string for PBKDF2 with custom parameters. * From 3a5bac0b6520c29fa3fd8bbe4c196c959828274f Mon Sep 17 00:00:00 2001 From: Randil Date: Fri, 11 Apr 2025 20:00:34 +0530 Subject: [PATCH 26/26] Update spec for PBKDF2 --- docs/spec/spec.md | 217 ++++++++++++++++++++++------------------------ 1 file changed, 103 insertions(+), 114 deletions(-) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 66570c50..28830500 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -4,7 +4,7 @@ _Owners_: @shafreenAnfar @bhashinee _Reviewers_: @shafreenAnfar _Created_: 2022/08/23 _Updated_: 2025/01/20 -_Edition_: Swan Lake +_Edition_: Swan Lake ## Introduction @@ -17,97 +17,97 @@ If you have any feedback or suggestions about the library, start a discussion vi The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug. ## Contents - 1. [Overview](#1-overview) 2. [Hash](#2-hash) - - 2.1. [MD5](#21-md5) - - 2.2. [SHA1](#22-sha1) - - 2.3. [SHA256](#23-sha256) - - 2.4. [SHA384](#24-sha384) - - 2.5. [SHA512](#25-sha512) - - 2.6. [CRC32B](#26-crc32b) - - 2.7. [KECCAK256](#27-keccak256) + * 2.1. [MD5](#21-md5) + * 2.2. [SHA1](#22-sha1) + * 2.3. [SHA256](#23-sha256) + * 2.4. [SHA384](#24-sha384) + * 2.5. [SHA512](#25-sha512) + * 2.6. [CRC32B](#26-crc32b) + * 2.7. [KECCAK256](#27-keccak256) 3. [HMAC](#3-hmac) - - 3.1. [MD5](#31-md5) - - 3.2. [SHA1](#32-sha1) - - 3.3. [SHA256](#33-sha256) - - 3.4. [SHA384](#34-sha384) - - 3.5. [SHA512](#35-sha512) + * 3.1. [MD5](#31-md5) + * 3.2. [SHA1](#32-sha1) + * 3.3. [SHA256](#33-sha256) + * 3.4. [SHA384](#34-sha384) + * 3.5. [SHA512](#35-sha512) 4. [Decode private/public key](#4-decode-private-public-keys) - - 4.1. [Decode RSA Private key from PKCS12 file](#41-rsa-decode-private-key-from-pkcs12-file) - - 4.2. [Decode RSA Private key using Private key and Password](#42-decode-rsa-private-key-using-private-key-and-password) - - 4.3. [Decode RSA Private key using Private key content and Password](#43-decode-rsa-private-key-using-private-key-content-and-password) - - 4.4. [Decode RSA Public key from PKCS12 file](#44-decode-rsa-public-key-from-pkcs12-file) - - 4.5. [Decode RSA Public key from the certificate file](#45-decode-rsa-public-key-from-the-certificate-file) - - 4.6. [Decode RSA Public key from the certificate content](#46-decode-rsa-public-key-from-the-certificate-content) - - 4.7. [Decode EC Private key from PKCS12 file](#47-decode-ec-private-key-from-pkcs12-file) - - 4.8. [Decode EC Private key using Private key and Password](#48-decode-ec-private-key-using-private-key-and-password) - - 4.9. [Decode EC Public key from PKCS12 file](#49-decode-ec-public-key-from-pkcs12-file) - - 4.10. [Decode EC Public key from the certificate file](#410-decode-ec-public-key-from-the-certificate-file) - - 4.11. [Build RSA Public key from modulus and exponent parameters](#411-build-rsa-public-key-from-modulus-and-exponent-parameters) - - 4.12. [Decode ML-DSA-65 Private key from PKCS12 file](#412-decode-ml-dsa-65-private-key-from-pkcs12-file) - - 4.13. [Decode ML-DSA-65 Private key using Private key and Password](#413-decode-ml-dsa-65-private-key-using-private-key-and-password) - - 4.14. [Decode ML-DSA-65 Public key from PKCS12 file](#414-decode-ml-dsa-65-public-key-from-pkcs12-file) - - 4.15. [Decode ML-DSA-65 Public key from the certificate file](#415-decode-ml-dsa-65-public-key-from-the-certificate-file) - - 4.16. [Decode ML-KEM-768 Private key from PKCS12 file](#416-decode-ml-kem-768-private-key-from-pkcs12-file) - - 4.17. [Decode ML-KEM-768 Private key using Private key and Password](#417-decode-ml-kem-768-private-key-using-private-key-and-password) - - 4.18. [Decode ML-KEM-768 Public key from PKCS12 file](#418-decode-ml-kem-768-public-key-from-pkcs12-file) - - 4.19. [Decode ML-KEM-768 Public key from the certificate file](#419-decode-ml-kem-768-public-key-from-the-certificate-file) -5. [Encrypt-Decrypt](#5-encrypt-decrypt) - - 5.1. [Encryption](#51-encryption) - - 5.1.1. [RSA](#511-rsa) - - 5.1.2. [AES-CBC](#512-aes-cbc) - - 5.1.3. [AES-ECB](#513-aes-ecb) - - 5.1.4. [AES-GCM](#514-aes-gcm) - - 5.1.5. [PGP](#515-pgp) - - 5.2. [Decryption](#52-decryption) - - 5.2.1. [RSA-ECB](#521-rsa-ecb) - - 5.2.2. [AES-CBC](#522-aes-cbc) - - 5.2.3. [AES-ECB](#523-aes-ecb) - - 5.2.4. [AES-GCM](#524-aes-gcm) - - 5.2.5. [PGP](#525-pgp) + * 4.1. [Decode RSA Private key from PKCS12 file](#41-rsa-decode-private-key-from-pkcs12-file) + * 4.2. [Decode RSA Private key using Private key and Password](#42-decode-rsa-private-key-using-private-key-and-password) + * 4.3. [Decode RSA Private key using Private key content and Password](#43-decode-rsa-private-key-using-private-key-content-and-password) + * 4.4. [Decode RSA Public key from PKCS12 file](#44-decode-rsa-public-key-from-pkcs12-file) + * 4.5. [Decode RSA Public key from the certificate file](#45-decode-rsa-public-key-from-the-certificate-file) + * 4.6. [Decode RSA Public key from the certificate content](#46-decode-rsa-public-key-from-the-certificate-content) + * 4.7. [Decode EC Private key from PKCS12 file](#47-decode-ec-private-key-from-pkcs12-file) + * 4.8. [Decode EC Private key using Private key and Password](#48-decode-ec-private-key-using-private-key-and-password) + * 4.9. [Decode EC Public key from PKCS12 file](#49-decode-ec-public-key-from-pkcs12-file) + * 4.10. [Decode EC Public key from the certificate file](#410-decode-ec-public-key-from-the-certificate-file) + * 4.11. [Build RSA Public key from modulus and exponent parameters](#411-build-rsa-public-key-from-modulus-and-exponent-parameters) + * 4.12. [Decode ML-DSA-65 Private key from PKCS12 file](#412-decode-ml-dsa-65-private-key-from-pkcs12-file) + * 4.13. [Decode ML-DSA-65 Private key using Private key and Password](#413-decode-ml-dsa-65-private-key-using-private-key-and-password) + * 4.14. [Decode ML-DSA-65 Public key from PKCS12 file](#414-decode-ml-dsa-65-public-key-from-pkcs12-file) + * 4.15. [Decode ML-DSA-65 Public key from the certificate file](#415-decode-ml-dsa-65-public-key-from-the-certificate-file) + * 4.16. [Decode ML-KEM-768 Private key from PKCS12 file](#416-decode-ml-kem-768-private-key-from-pkcs12-file) + * 4.17. [Decode ML-KEM-768 Private key using Private key and Password](#417-decode-ml-kem-768-private-key-using-private-key-and-password) + * 4.18. [Decode ML-KEM-768 Public key from PKCS12 file](#418-decode-ml-kem-768-public-key-from-pkcs12-file) + * 4.19. [Decode ML-KEM-768 Public key from the certificate file](#419-decode-ml-kem-768-public-key-from-the-certificate-file) +5. [Encrypt-Decrypt](#5-encrypt-decrypt) + * 5.1. [Encryption](#51-encryption) + * 5.1.1. [RSA](#511-rsa) + * 5.1.2. [AES-CBC](#512-aes-cbc) + * 5.1.3. [AES-ECB](#513-aes-ecb) + * 5.1.4. [AES-GCM](#514-aes-gcm) + * 5.1.5. [PGP](#515-pgp) + * 5.2. [Decryption](#52-decryption) + * 5.2.1. [RSA-ECB](#521-rsa-ecb) + * 5.2.2. [AES-CBC](#522-aes-cbc) + * 5.2.3. [AES-ECB](#523-aes-ecb) + * 5.2.4. [AES-GCM](#524-aes-gcm) + * 5.2.5. [PGP](#525-pgp) 6. [Sign and Verify](#6-sign-and-verify) - - 6.1. [Sign messages](#61-sign-messages) - - 6.1.1. [RSA-MD5](#611-rsa-md5) - - 6.1.2. [RSA-SHA1](#612-rsa-sha1) - - 6.1.3. [RSA-SHA256](#613-rsa-sha256) - - 6.1.4. [RSA-SHA384](#614-rsa-sha384) - - 6.1.5. [RSA-SHA512](#615-rsa-sha512) - - 6.1.6. [SHA384withECDSA](#616-sha384withecdsa) - - 6.1.7. [SHA256withECDSA](#617-sha256withecdsa) - - 6.1.8. [ML-DSA-65](#618-mldsa65) - - 6.2. [Verify signature](#62-verify-signature) - - 6.2.1. [RSA-MD5](#621-rsa-md5) - - 6.2.2. [RSA-SHA1](#622-rsa-sha1) - - 6.2.3. [RSA-SHA256](#623-rsa-sha256) - - 6.2.4. [RSA-SHA384](#624-rsa-sha384) - - 6.2.5. [RSA-SHA512](#625-rsa-sha512) - - 6.2.6. [SHA384withECDSA](#626-sha384withecdsa) - - 6.2.7. [SHA256withECDSA](#627-sha256withecdsa) - - 6.2.8. [ML-DSA-65](#618-mldsa65) + * 6.1. [Sign messages](#61-sign-messages) + * 6.1.1. [RSA-MD5](#611-rsa-md5) + * 6.1.2. [RSA-SHA1](#612-rsa-sha1) + * 6.1.3. [RSA-SHA256](#613-rsa-sha256) + * 6.1.4. [RSA-SHA384](#614-rsa-sha384) + * 6.1.5. [RSA-SHA512](#615-rsa-sha512) + * 6.1.6. [SHA384withECDSA](#616-sha384withecdsa) + * 6.1.7. [SHA256withECDSA](#617-sha256withecdsa) + * 6.1.8. [ML-DSA-65](#618-mldsa65) + * 6.2. [Verify signature](#62-verify-signature) + * 6.2.1. [RSA-MD5](#621-rsa-md5) + * 6.2.2. [RSA-SHA1](#622-rsa-sha1) + * 6.2.3. [RSA-SHA256](#623-rsa-sha256) + * 6.2.4. [RSA-SHA384](#624-rsa-sha384) + * 6.2.5. [RSA-SHA512](#625-rsa-sha512) + * 6.2.6. [SHA384withECDSA](#626-sha384withecdsa) + * 6.2.7. [SHA256withECDSA](#627-sha256withecdsa) + * 6.2.8. [ML-DSA-65](#618-mldsa65) 7. [Key Derivation Function (KDF)](#7-key-derivation-function-kdf) - - 7.1. [HKDF-SHA256](#71-hkdf-sha256) + * 7.1. [HKDF-SHA256](#71-hkdf-sha256) 8. [Key Exchange Mechanism (KEM)](#8-key-exchange-mechanism-kem) - - 8.1 [Encapsulation](#81-encapsulation) - - 8.1.1 [RSA-KEM](#811-rsa-kem) - - 8.1.2 [ML-KEM-768](#812-ml-kem-768) - - 8.1.3 [RSA-KEM-ML-KEM-768](#813-rsa-kem-ml-kem-768) - - 8.2 [Decapsulation](#81-encapsulation) - - 8.2.1 [RSA-KEM](#821-rsa-kem) - - 8.2.2 [ML-KEM-768](#822-ml-kem-768) - - 8.2.3 [RSA-KEM-ML-KEM-768](#823-rsa-kem-ml-kem-768) + * 8.1 [Encapsulation](#81-encapsulation) + * 8.1.1 [RSA-KEM](#811-rsa-kem) + * 8.1.2 [ML-KEM-768](#812-ml-kem-768) + * 8.1.3 [RSA-KEM-ML-KEM-768](#813-rsa-kem-ml-kem-768) + * 8.2 [Decapsulation](#81-encapsulation) + * 8.2.1 [RSA-KEM](#821-rsa-kem) + * 8.2.2 [ML-KEM-768](#822-ml-kem-768) + * 8.2.3 [RSA-KEM-ML-KEM-768](#823-rsa-kem-ml-kem-768) 9. [Hybrid Public Key Encryption (HPKE)](#9-hybrid-public-key-encryption-hpke) - - 9.1 [Encrypt](#91-encrypt) - - 9.1.1 [ML-KEM-768-HPKE](#911-ml-kem-768-hpke) - - 9.1.2 [RSA-KEM-ML-KEM-768-HPKE](#912-rsa-kem-ml-kem-768-hpke) - - 9.2 [Decrypt](#92-decrypt) - - 9.2.1 [ML-KEM-768-HPKE](#921-ml-kem-768-hpke) - - 9.2.2 [RSA-KEM-ML-KEM-768-HPKE](#922-rsa-kem-ml-kem-768-hpke) + * 9.1 [Encrypt](#91-encrypt) + * 9.1.1 [ML-KEM-768-HPKE](#911-ml-kem-768-hpke) + * 9.1.2 [RSA-KEM-ML-KEM-768-HPKE](#912-rsa-kem-ml-kem-768-hpke) + * 9.2 [Decrypt](#92-decrypt) + * 9.2.1 [ML-KEM-768-HPKE](#921-ml-kem-768-hpke) + * 9.2.2 [RSA-KEM-ML-KEM-768-HPKE](#922-rsa-kem-ml-kem-768-hpke) 10. [Password hashing](#10-password-hashing) - - 10.1 [BCrypt](#101-bcrypt) - - 10.2 [Argon2](#102-argon2) - - 10.3 [PBKDF2](#103-pbkdf2) + * 10.1 [BCrypt](#101-bcrypt) + * 10.2 [Argon2](#102-argon2) + * 10.3 [PBKDF2](#103-pbkdf2) + ## 1. [Overview](#1-overview) The Ballerina `crypto` library facilitates APIs to do operations like hashing, HMAC generation, checksum generation, encryption, decryption, signing data digitally, verifying digitally signed data, etc., with different cryptographic algorithms. @@ -119,7 +119,6 @@ The `crypto` library supports generating hashes with 5 different hash algorithms ### 2.1. [MD5](#21-md) This API can be used to create the MD5 hash of the given data. - ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -129,7 +128,6 @@ byte[] hash = crypto:hashMd5(data); ### 2.2. [SHA1](#22-sha1) This API can be used to create the SHA-1 hash of the given data. - ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -139,7 +137,6 @@ byte[] hash = crypto:hashSha1(data); ### 2.3. [SHA256](#23-sha256) This API can be used to create the SHA-256 hash of the given data. - ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -149,7 +146,6 @@ byte[] hash = crypto:hashSha256(data); ### 2.4. [SHA384](#24-sha384) This API can be used to create the SHA-384 hash of the given data. - ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -159,7 +155,6 @@ byte[] hash = crypto:hashSha384(data); ### 2.5. [SHA512](#25-sha512) This API can be used to create the SHA-512 hash of the given data. - ```ballerina string dataString = "Hello Ballerina"; byte[] data = dataString.toBytes(); @@ -169,7 +164,6 @@ byte[] hash = crypto:hashSha512(data); ### 2.6. [CRC32B](#26-crc32b) This API can be used to create the Hex-encoded CRC32B value of the given data. - ```ballerina string stringData = "Hello Ballerina"; byte[] data = stringData.toBytes(); @@ -179,7 +173,6 @@ string checksum = crypto:crc32b(data); ### 2.7. [KECCAK256](#27-keccak256) This API can be used to create the Hex-encoded KECCAK-256 value of the given data. - ```ballerina string stringData = "Hello Ballerina"; byte[] data = stringData.toBytes(); @@ -453,7 +446,7 @@ string certFile = "/path/to/public.cert"; crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromCertFile(certFile); ``` -## 5. [Encrypt-Decrypt](#5-encrypt-decrypt) +## 5. [Encrypt-Decrypt](#5-encrypt-decrypt) The `crypto` library supports both symmetric key encryption/decryption and asymmetric key encryption/decryption. The RSA algorithm can be used for asymmetric-key encryption/decryption with the use of private and public keys. The AES algorithm can be used for symmetric-key encryption/decryption with the use of a shared key. @@ -539,7 +532,7 @@ byte[] cipherText = check crypto:encryptPgp(data, publicKeyPath); The following encryption options can be configured in the PGP encryption. | Option | Description | Default Value | -| --------------------- | ----------------------------------------------------------------- | ------------- | +|-----------------------|-------------------------------------------------------------------|---------------| | compressionAlgorithm | Specifies the compression algorithm used for PGP encryption | ZIP | | symmetricKeyAlgorithm | Specifies the symmetric key algorithm used for encryption | AES_256 | | armor | Indicates whether ASCII armor is enabled for the encrypted output | true | @@ -920,6 +913,7 @@ crypto:PublicKey publicKey = check crypto:decodeMlDsa65PublicKeyFromTrustStore(k boolean validity = check crypto:verifyMlDsa65Signature(data, signature, publicKey); ``` + ## 7. [Key Derivation Function (KDF)](#7-key-derivation-function-kdf) The `crypto` module supports HMAC-based Key Derivation Function (HKDF). HKDF is a key derivation function that uses a Hash-based Message Authentication Code (HMAC) to derive keys. @@ -939,10 +933,10 @@ byte[] hash = crypto:hkdfSha256(key, 32); The `crypto` module supports Key Exchange Mechanisms (KEM). It includes RSA-KEM and post-quantum ML-KEM-768 for both encapsulation and decapsulation. ### 8.1. [Encapsulation](#81-encapsulation) - + #### 8.1.1. [RSA-KEM](#811-rsa-kem) -This API can be used to create shared secret and its encapsulation using RSA-KEM function. +This API can be used to create shared secret and its encapsulation using RSA-KEM function. ```ballerina crypto:KeyStore keyStore = { @@ -952,10 +946,10 @@ crypto:KeyStore keyStore = { crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, "keyAlias"); crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateRsaKem(publicKey); ``` - + #### 8.1.2. [ML-KEM-768](#812-ml-kem-768) -This API can be used to create shared secret and its encapsulation using ML-KEM-768 function. +This API can be used to create shared secret and its encapsulation using ML-KEM-768 function. ```ballerina crypto:KeyStore keyStore = { @@ -965,10 +959,10 @@ crypto:KeyStore keyStore = { crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias"); crypto:EncapsulationResult encapsulationResult = check crypto:encapsulateMlKem768(publicKey); ``` - + #### 8.1.3. [RSA-KEM-ML-KEM-768](#813-rsa-kem-ml-kem-768) -This API can be used to create shared secret and its encapsulation using RSA-KEM-ML-KEM-768 function. +This API can be used to create shared secret and its encapsulation using RSA-KEM-ML-KEM-768 function. ```ballerina crypto:KeyStore mlkemKeyStore = { @@ -989,10 +983,10 @@ byte[] sharedSecret = check crypto:decapsulateRsaKemMlKem768(encapsulatedSecret, ``` ### 8.2. [Decapsulation](#81-encapsulation) - + #### 8.2.1. [RSA-KEM](#821-rsa-kem) -This API can be used to decapsulate shared secret using RSA-KEM function of the given data. +This API can be used to decapsulate shared secret using RSA-KEM function of the given data. ```ballerina crypto:KeyStore keyStore = { @@ -1005,10 +999,10 @@ byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret; crypto:PrivateKey privateKey = check crypto:decodeRsaPrivateKeyFromKeyStore(keyStore, "keyAlias", "keyStorePassword"); byte[] sharedSecret = check crypto:decapsulateRsaKem(encapsulatedSecret, privateKey); ``` - + #### 8.2.2. [ML-KEM-768](#822-ml-kem-768) -This API can be used to decapsulate shared secret using ML-KEM-768 function of the given data. +This API can be used to decapsulate shared secret using ML-KEM-768 function of the given data. ```ballerina crypto:KeyStore keyStore = { @@ -1021,10 +1015,10 @@ byte[] encapsulatedSecret = encapsulationResult.encapsulatedSecret; crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(keyStore, "keyAlias", "keyStorePassword"); byte[] sharedSecret = check crypto:decapsulateMlKem768(encapsulatedSecret, privateKey); ``` - + #### 8.2.3. [RSA-KEM-ML-KEM-768](#823-rsa-kem-ml-kem-768) -This API can be used to decapsulate shared secret using RSA-KEM-ML-KEM-768 function of the given data. +This API can be used to decapsulate shared secret using RSA-KEM-ML-KEM-768 function of the given data. ```ballerina crypto:KeyStore mlkemKeyStore = { @@ -1049,7 +1043,7 @@ byte[] sharedSecret = check crypto:decapsulateRsaKemMlKem768(encapsulatedSecret, The `crypto` module supports Hybrid Public Key Encryption (HPKE). It supports post-quantum ML-KEM-768-HPKE and RSA-KEM-ML-KEM-768-HPKE for encryption and decryption. ### 9.1. [Encrypt](#91-encrypt) - + #### 9.1.1. [ML-KEM-768-HPKE](#911-ml-kem-768-hpke) This API can be used to create the ML-KEM-768-hybrid-encrypted value of the given data. @@ -1064,7 +1058,7 @@ crypto:KeyStore keyStore = { crypto:PublicKey publicKey = check crypto:decodeMlKem768PublicKeyFromTrustStore(keyStore, "keyAlias"); crypto:HybridEncryptionResult encryptionResult = check crypto:encryptMlKem768Hpke(data, publicKey); ``` - + #### 9.1.2. [RSA-KEM-ML-KEM-768-HPKE](#912-rsa-kem-ml-kem-768-hpke) This API can be used to create the RSA-KEM-ML-KEM-768-hybrid-encrypted value of the given data. @@ -1086,7 +1080,7 @@ crypto:HybridEncryptionResult encryptionResult = check crypto:encryptRsaKemMlKem ``` ### 9.2. [Decrypt](#92-decrypt) - + #### 9.2.1. [ML-KEM-768-HPKE](#921-ml-kem-768-hpke) This API can be used to create the ML-KEM-768-hybrid-decrypted value of the given data. @@ -1105,7 +1099,7 @@ byte[] encapsulatedKey = encryptionResult.encapsulatedSecret; crypto:PrivateKey privateKey = check crypto:decodeMlKem768PrivateKeyFromKeyStore(keyStore, "keyAlias", "keyStorePassword"); byte[] decryptedData = check crypto:decryptMlKem768Hpke(cipherText, encapsulatedKey, privateKey); ``` - + #### 9.2.2. [RSA-KEM-ML-KEM-768-HPKE](#922-rsa-kem-ml-kem-768-hpke) This API can be used to create the RSA-KEM-ML-KEM-768-hybrid-decrypted value of the given data. @@ -1144,7 +1138,6 @@ public isolated function hashBcrypt(string password, int workFactor = 12) return ``` Parameters: - - `password`: The plain text password to hash - `workFactor`: Computational complexity factor (4-31, default: 12) @@ -1153,7 +1146,6 @@ public isolated function verifyBcrypt(string password, string hashedPassword) re ``` Example: - ```ballerina string password = "your-password"; // Hash with default work factor (12) @@ -1168,12 +1160,11 @@ boolean isValid = check crypto:verifyBcrypt(password, hashedPassword1); Implements the Argon2id variant of the Argon2 password hashing algorithm, optimized for both high memory usage and GPU resistance. ```ballerina -public isolated function hashArgon2(string password, int iterations = 3, +public isolated function hashArgon2(string password, int iterations = 3, int memory = 65536, int parallelism = 4) returns string|Error ``` Parameters: - - `password`: The plain text password to hash - `iterations`: Number of iterations (default: 3) - `memory`: Memory usage in KB (minimum: 8192, default: 65536) @@ -1186,7 +1177,6 @@ public isolated function verifyArgon2(string password, string hashedPassword) re ``` Example: - ```ballerina string password = "your-password"; // Hash with default parameters @@ -1206,7 +1196,6 @@ public enum HmacAlgorithm { SHA256, SHA512 } - public isolated function hashPbkdf2(string password, int iterations = 10000, HmacAlgorithm algorithm = SHA256) returns string|Error ``` @@ -1243,4 +1232,4 @@ string password = "mySecurePassword123"; string hashedPassword = "$pbkdf2-sha256$i=10000$salt$hash"; // Verify the hashed password boolean isValid = check crypto:verifyPbkdf2(password, hashedPassword); -``` +``` \ No newline at end of file