Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "crypto"
version = "2.9.1"
version = "2.10.0"
authors = ["Ballerina"]
keywords = ["security", "hash", "hmac", "sign", "encrypt", "decrypt", "private key", "public key"]
repository = "https://github.com/ballerina-platform/module-ballerina-crypto"
Expand All @@ -15,8 +15,8 @@ graalvmCompatible = true
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "crypto-native"
version = "2.9.1"
path = "../native/build/libs/crypto-native-2.9.1-SNAPSHOT.jar"
version = "2.10.0"
path = "../native/build/libs/crypto-native-2.10.0-SNAPSHOT.jar"

[[platform.java21.dependency]]
groupId = "org.bouncycastle"
Expand Down
2 changes: 1 addition & 1 deletion ballerina/CompilerPlugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ id = "crypto-compiler-plugin"
class = "io.ballerina.stdlib.crypto.compiler.CryptoCompilerPlugin"

[[dependency]]
path = "../compiler-plugin/build/libs/crypto-compiler-plugin-2.9.1-SNAPSHOT.jar"
path = "../compiler-plugin/build/libs/crypto-compiler-plugin-2.10.0-SNAPSHOT.jar"
2 changes: 1 addition & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ distribution-version = "2201.12.0"
[[package]]
org = "ballerina"
name = "crypto"
version = "2.9.1"
version = "2.10.0"
dependencies = [
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
Expand Down
13 changes: 10 additions & 3 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added
- [Introduce support for PBKDF2 password hashing and verification](https://github.com/ballerina-platform/ballerina-lang/issues/43926)
- [Add static analysis rule - Encryption algorithms should be used with secure mode and padding scheme](https://github.com/ballerina-platform/ballerina-library/issues/7940)
- [Add static analysis rule - Passwords should not be stored in plaintext or with a fast hashing algorithm](https://github.com/ballerina-platform/ballerina-library/issues/7950)
- [Add static analysis rule - Counter Mode initialization vectors should not be reused](https://github.com/ballerina-platform/ballerina-library/issues/8010)

### Changed
- [Update OIDS of NIST approved post quantum algorithms](https://github.com/ballerina-platform/ballerina-library/issues/7678)
- [Optimize hardcoded IV detection using semantic model reference counting](https://github.com/ballerina-platform/ballerina-library/issues/8257)
## [2.9.1] - 2025-09-29

### Fixed
- [Implement optional close method check for BStream](https://github.com/ballerina-platform/ballerina-library/issues/8288)

## [2.9.0] - 2025-03-12

### Changed
- [Update OIDS of NIST approved post quantum algorithms](https://github.com/ballerina-platform/ballerina-library/issues/7678)


## [2.8.0] - 2025-02-11

### Added
Expand Down
13 changes: 13 additions & 0 deletions compiler-plugin-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ plugins {
id 'java'
id 'checkstyle'
id 'com.github.spotbugs'
id 'jacoco'
}

description = 'Ballerina - Crypto Compiler Plugin Tests'
Expand Down Expand Up @@ -61,6 +62,18 @@ test {
}
}
}
jacoco {
destinationFile = file("$buildDir/jacoco/test.exec")
}
finalizedBy jacocoTestReport
}

jacocoTestReport {
dependsOn test
reports {
xml.required = true
}
sourceSets project(':crypto-compiler-plugin').sourceSets.main
}

spotbugsTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
import static io.ballerina.scan.RuleKind.VULNERABILITY;
import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_FAST_HASH_ALGORITHMS;
import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_REUSING_COUNTER_MODE_VECTORS;
import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_USING_UNSECURE_RANDOM_NUMBER_GENERATORS;
import static io.ballerina.stdlib.crypto.compiler.staticcodeanalyzer.CryptoRule.AVOID_WEAK_CIPHER_ALGORITHMS;
import static java.nio.charset.StandardCharsets.UTF_8;

Expand Down Expand Up @@ -118,89 +117,127 @@ private void validateRules(List<Rule> rules) {
"ballerina/crypto:3",
AVOID_REUSING_COUNTER_MODE_VECTORS.getDescription(),
VULNERABILITY);
Assertions.assertRule(
rules,
"ballerina/crypto:4",
AVOID_USING_UNSECURE_RANDOM_NUMBER_GENERATORS.getDescription(),
VULNERABILITY);
}

private void validateIssues(CryptoRule rule, List<Issue> issues) {
int index;
switch (rule) {
case AVOID_WEAK_CIPHER_ALGORITHMS:
Assert.assertEquals(issues.size(), 6);
Assertions.assertIssue(issues, 0, "ballerina/crypto:1", "aes_cbc.bal",
30, 30, Source.BUILT_IN);
Assertions.assertIssue(issues, 1, "ballerina/crypto:4", "aes_cbc.bal",
index = 0;
Assert.assertEquals(issues.size(), 10);
Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "aes_cbc.bal",
30, 30, Source.BUILT_IN);
Assertions.assertIssue(issues, 2, "ballerina/crypto:1", "aes_cbc_as_import.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "aes_cbc_as_import.bal",
30, 30, Source.BUILT_IN);
Assertions.assertIssue(issues, 3, "ballerina/crypto:4", "aes_cbc_as_import.bal",
30, 30, Source.BUILT_IN);
Assertions.assertIssue(issues, 4, "ballerina/crypto:1", "aes_ecb.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "aes_ecb.bal",
26, 26, Source.BUILT_IN);
Assertions.assertIssue(issues, 5, "ballerina/crypto:1", "aes_ecb_as_import.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "aes_ecb_as_import.bal",
26, 26, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "weak_cipher_algo.bal",
23, 23, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "weak_cipher_algo.bal",
27, 27, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "weak_cipher_algo.bal",
27, 27, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "weak_cipher_algo.bal",
35, 35, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:1", "weak_cipher_algo.bal",
36, 36, Source.BUILT_IN);
Assertions.assertIssue(issues, index, "ballerina/crypto:1", "weak_cipher_algo.bal",
39, 39, Source.BUILT_IN);
break;
case AVOID_FAST_HASH_ALGORITHMS:
Assert.assertEquals(issues.size(), 18);
Assertions.assertIssue(issues, 0, "ballerina/crypto:2", "argon_func_var_named_arg.bal",
Assert.assertEquals(issues.size(), 29);
index = 0;
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func.bal",
31, 31, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func.bal",
33, 33, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func.bal",
35, 35, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func.bal",
37, 37, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func_var_named_arg.bal",
23, 23, Source.BUILT_IN);
Assertions.assertIssue(issues, 1, "ballerina/crypto:2", "argon_func_var_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_func_var_pos_arg.bal",
23, 23, Source.BUILT_IN);
Assertions.assertIssue(issues, 2, "ballerina/crypto:2", "argon_inline_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_inline_named_arg.bal",
20, 20, Source.BUILT_IN);
Assertions.assertIssue(issues, 3, "ballerina/crypto:2", "argon_inline_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_inline_pos_arg.bal",
20, 20, Source.BUILT_IN);
Assertions.assertIssue(issues, 4, "ballerina/crypto:2", "argon_mod_var_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_mod_var_named_arg.bal",
24, 24, Source.BUILT_IN);
Assertions.assertIssue(issues, 5, "ballerina/crypto:2", "argon_mod_var_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "argon_mod_var_pos_arg.bal",
24, 24, Source.BUILT_IN);
Assertions.assertIssue(issues, 6, "ballerina/crypto:2", "bcrypt_func_var_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_func_var_named_arg.bal",
21, 21, Source.BUILT_IN);
Assertions.assertIssue(issues, 7, "ballerina/crypto:2", "bcrypt_func_var_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_func_var_pos_arg.bal",
21, 21, Source.BUILT_IN);
Assertions.assertIssue(issues, 8, "ballerina/crypto:2", "bcrypt_inline_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_inline_named_arg.bal",
20, 20, Source.BUILT_IN);
Assertions.assertIssue(issues, 9, "ballerina/crypto:2", "bcrypt_inline_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_inline_pos_arg.bal",
20, 20, Source.BUILT_IN);
Assertions.assertIssue(issues, 10, "ballerina/crypto:2", "bcrypt_mod_var_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_mod_var_named_arg.bal",
22, 22, Source.BUILT_IN);
Assertions.assertIssue(issues, 11, "ballerina/crypto:2", "bcrypt_mod_var_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "bcrypt_mod_var_pos_arg.bal",
22, 22, Source.BUILT_IN);
Assertions.assertIssue(issues, 12, "ballerina/crypto:2", "pbkdf2_func_var_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal",
21, 21, Source.BUILT_IN);
Assertions.assertIssue(issues, 13, "ballerina/crypto:2", "pbkdf2_func_var_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal",
22, 22, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal",
23, 23, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal",
24, 24, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal",
38, 38, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal",
39, 39, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func.bal",
40, 40, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func_var_named_arg.bal",
21, 21, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_func_var_pos_arg.bal",
21, 21, Source.BUILT_IN);
Assertions.assertIssue(issues, 14, "ballerina/crypto:2", "pbkdf2_inline_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_inline_named_arg.bal",
20, 20, Source.BUILT_IN);
Assertions.assertIssue(issues, 15, "ballerina/crypto:2", "pbkdf2_inline_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_inline_pos_arg.bal",
20, 20, Source.BUILT_IN);
Assertions.assertIssue(issues, 16, "ballerina/crypto:2", "pbkdf2_mod_var_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:2", "pbkdf2_mod_var_named_arg.bal",
22, 22, Source.BUILT_IN);
Assertions.assertIssue(issues, 17, "ballerina/crypto:2", "pbkdf2_mod_var_pos_arg.bal",
Assertions.assertIssue(issues, index, "ballerina/crypto:2", "pbkdf2_mod_var_pos_arg.bal",
22, 22, Source.BUILT_IN);
break;
case AVOID_REUSING_COUNTER_MODE_VECTORS:
Assert.assertEquals(issues.size(), 6);
Assertions.assertIssue(issues, 0, "ballerina/crypto:3", "func_var_named_arg.bal",
Assert.assertEquals(issues.size(), 13);
index = 0;
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal",
24, 24, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal",
26, 26, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal",
29, 29, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal",
32, 32, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal",
36, 36, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal",
39, 39, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_hardcoded_iv_param.bal",
42, 42, Source.BUILT_IN);
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_var_named_arg.bal",
22, 22, Source.BUILT_IN);
Assertions.assertIssue(issues, 1, "ballerina/crypto:3", "func_var_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "func_var_pos_arg.bal",
22, 22, Source.BUILT_IN);
Assertions.assertIssue(issues, 2, "ballerina/crypto:3", "inline_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "inline_named_arg.bal",
21, 21, Source.BUILT_IN);
Assertions.assertIssue(issues, 3, "ballerina/crypto:3", "inline_pos_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "inline_pos_arg.bal",
21, 21, Source.BUILT_IN);
Assertions.assertIssue(issues, 4, "ballerina/crypto:3", "mod_var_named_arg.bal",
Assertions.assertIssue(issues, index++, "ballerina/crypto:3", "mod_var_named_arg.bal",
23, 23, Source.BUILT_IN);
Assertions.assertIssue(issues, 5, "ballerina/crypto:3", "mod_var_pos_arg.bal",
Assertions.assertIssue(issues, index, "ballerina/crypto:3", "mod_var_pos_arg.bal",
23, 23, Source.BUILT_IN);
break;
case AVOID_USING_UNSECURE_RANDOM_NUMBER_GENERATORS:
Assert.assertEquals(issues.size(), 1);
Assertions.assertIssue(issues, 0, "ballerina/crypto:4", "main.bal",
27, 27, Source.BUILT_IN);
break;
default:
Assert.fail("Unhandled rule in validateIssues: " + rule);
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org)
// 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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org)
// 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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org)
// 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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org)
// 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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.

import ballerina/crypto;

public function main() returns error? {
byte[] inputBytes = "input".toBytes();
byte[] keyBytes = "key".toBytes();

// Encrypt using AES ECB mode is not secure
_ = check crypto:encryptAesEcb(inputBytes, keyBytes);

byte[] iv = "1234567890123456".toBytes();
// Encrypt using AES CBC mode is secure
_ = check crypto:encryptAesCbc(inputBytes, keyBytes, iv);

crypto:KeyStore keyStore = {
path: "/path/to/keyStore.p12",
password: "keyStorePassword"
};
crypto:PublicKey publicKey = check crypto:decodeRsaPublicKeyFromTrustStore(keyStore, "keyAlias");
// Encrypt using RSA ECB with PKCS1 padding is not secure
_ = check crypto:encryptRsaEcb(inputBytes, publicKey, crypto:PKCS1);
_ = check crypto:encryptRsaEcb(inputBytes, publicKey, "PKCS1");

// Default RSA ECB encryption uses PKCS1 padding. Hence, it is not secure
_ = check crypto:encryptRsaEcb(inputBytes, publicKey);

// Encrypt using RSA ECB with OAEP padding is secure
_ = check crypto:encryptRsaEcb(inputBytes, publicKey, crypto:OAEPWithSHA1AndMGF1);
_ = check crypto:encryptRsaEcb(inputBytes, publicKey, "OAEPWithSHA1AndMGF1");

}
Loading