Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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