Skip to content

Commit

Permalink
Crypto enhancement (#119)
Browse files Browse the repository at this point in the history
* Added IV as a parameter for encrypt and decrypt

* Vulnerabilities fixes
  • Loading branch information
javaduke authored Apr 26, 2023
1 parent 8e460c9 commit 9a5aaf5
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 34 deletions.
14 changes: 8 additions & 6 deletions docs/modules/ROOT/pages/libraries-crypto.adoc
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
## crypto

### `decrypt(string value, string secret, string algorithm, string mode, string padding)`
### `decrypt(string value, string secret, string transformation, string iv = null)`
Decrypts the Base64 value with specified JDK Cipher Transformation string and the provided secret.

The transformation string describes the operation (or set of operations) to be performed on the given input, to produce some output. A transformation always includes the name of a cryptographic algorithm (e.g., AES), and may be followed by a feedback mode and padding scheme. A transformation is of the form: "algorithm/mode/padding" or "algorithm". See https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/crypto/Cipher.html[Java Cipher] for more information.
The optional IV parameter sets the initialization vector, if null or not specified, a random one will be generated.

*Example:*

------------------------
ds.crypto.decrypt("Hello World", "DataSonnet123456", "AES/ECB/PKCS5Padding")
ds.crypto.decrypt("HrkF1grBXCtATMLxh1gZVA==", "DataSonnet123456", "AES/ECB/PKCS5Padding")
------------------------
.Result
------------------------
"HrkF1grBXCtATMLxh1gZVA=="
"Hello World"
------------------------

### `encrypt(string value, string secret, string transformation)`
### `encrypt(string value, string secret, string transformation, string iv = null)`
Encrypts the value with specified JDK Cipher Transformation and the provided secret. Converts the encryption to a readable format with Base64.

The transformation string describes the operation (or set of operations) to be performed on the given input, to produce some output. A transformation always includes the name of a cryptographic algorithm (e.g., AES), and may be followed by a feedback mode and padding scheme. A transformation is of the form: "algorithm/mode/padding" or "algorithm". See https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/crypto/Cipher.html[Java Cipher] for more information.
The optional IV parameter sets the initialization vector, if null or not specified, a random one will be generated.

*Example:*

------------------------
ds.crypto.decrypt("HrkF1grBXCtATMLxh1gZVA==", "DataSonnet123456", "AES/ECB/PKCS5Padding")
ds.crypto.encrypt("Hello World", "DataSonnet123456", "AES/ECB/PKCS5Padding")
------------------------
.Result
------------------------
"Hello World"
"HrkF1grBXCtATMLxh1gZVA=="
------------------------

### `hash(string value, string algorithm)`
Expand Down
11 changes: 6 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<jackson.version>2.14.0</jackson.version>
<jackson.version>2.15.0</jackson.version>
<junit.version>5.6.2</junit.version>
<bouncycastle.version>1.68</bouncycastle.version>
<bouncycastle.version>1.69</bouncycastle.version>
<quickcheck.version>0.9.4</quickcheck.version>
<xmlunit.version>2.7.0</xmlunit.version>
<json-path.version>2.7.0</json-path.version>
<json-path.version>2.8.0</json-path.version>

<mycila.version>4.1</mycila.version>

Expand Down Expand Up @@ -292,6 +292,7 @@
<scope>test</scope>
</dependency>
<!-- test deps: end -->

</dependencies>

<build>
Expand Down Expand Up @@ -608,7 +609,7 @@
</execution>
</executions>
</plugin>
<plugin>
<!-- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
Expand All @@ -620,7 +621,7 @@
</archive>
<classifier>jakarta4</classifier>
</configuration>
</plugin>
</plugin>-->
</plugins>
</build>
</profile>
Expand Down
57 changes: 36 additions & 21 deletions src/main/scala/com/datasonnet/DS.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1181,16 +1181,17 @@ object DSLowercase extends Library {
* (e.g., AES), and may be followed by a feedback mode and padding scheme. A transformation is of the form:
* "algorithm/mode/padding" or "algorithm"
* @types [String]
* @builtinParam iv Optional initialization vector.
* @types [String]
* @builtinReturn Base64 String value of the encrypted message
* @types [String]
* @changed 2.0.3
*/
builtin0[Val]("encrypt", "value", "secret", "transformation") {
(vals, ev, fs) =>
val valSeq = validate(vals, ev, fs, Array(StringRead, StringRead, StringRead))
val value = valSeq(0).asInstanceOf[String]
val secret = valSeq(1).asInstanceOf[String]
val transformation = valSeq(2).asInstanceOf[String]
builtinWithDefaults[Val]("encrypt", "value" -> None, "secret" -> None, "transformation" -> None, "iv" -> Some(Expr.Null(0))) {
(args, ev) =>
val value = args("value").asInstanceOf[Val.Str].value
val secret = args("secret").asInstanceOf[Val.Str].value
val transformation = args("transformation").asInstanceOf[Val.Str].value

val cipher = Cipher.getInstance(transformation)
val transformTokens = transformation.split("/")
Expand All @@ -1203,8 +1204,14 @@ object DSLowercase extends Library {
} else {
// https://stackoverflow.com/a/52571774/4814697
val rand: SecureRandom = new SecureRandom()
val iv = new Array[Byte](cipher.getBlockSize)
rand.nextBytes(iv)

val iv: Array[Byte] = if (args("iv") == Val.Null) {
val newIV = new Array[Byte](cipher.getBlockSize)
rand.nextBytes(newIV)
newIV
} else {
args("iv").asInstanceOf[Val.Str].value.getBytes
}

cipher.init(Cipher.ENCRYPT_MODE,
new SecretKeySpec(secret.getBytes, transformTokens(0).toUpperCase),
Expand Down Expand Up @@ -1233,22 +1240,22 @@ object DSLowercase extends Library {
* @types [String]
* @builtinParam secret The secret used to encrypt the original messsage.
* @types [String]
* @builtinParam algorithm The algorithm used for the encryption.
* @types [String]
* @builtinParam mode The encryption mode to be used.
* @builtinParam transformation The string that describes the operation (or set of operations) to be performed on
* the given input, to produce some output. A transformation always includes the name of a cryptographic algorithm
* (e.g., AES), and may be followed by a feedback mode and padding scheme. A transformation is of the form:
* "algorithm/mode/padding" or "algorithm"
* @types [String]
* @builtinParam padding The encryption secret padding to be used
* @builtinParam iv Optional initialization vector.
* @types [String]
* @builtinReturn Base64 String value of the encrypted message
* @builtinReturn String value of the decrypted message
* @types [String]
* @changed 2.0.3
*/
builtin0[Val]("decrypt", "value", "secret", "transformation") {
(vals, ev,fs) =>
val valSeq = validate(vals, ev, fs, Array(StringRead, StringRead, StringRead))
val value = valSeq(0).asInstanceOf[String]
val secret = valSeq(1).asInstanceOf[String]
val transformation = valSeq(2).asInstanceOf[String]
builtinWithDefaults[Val]("decrypt", "value" -> None, "secret" -> None, "transformation" -> None, "iv" -> Some(Expr.Null(0))) {
(args, ev) =>
val value = args("value").asInstanceOf[Val.Str].value
val secret = args("secret").asInstanceOf[Val.Str].value
val transformation = args("transformation").asInstanceOf[Val.Str].value

val cipher = Cipher.getInstance(transformation)
val transformTokens = transformation.split("/")
Expand All @@ -1262,10 +1269,18 @@ object DSLowercase extends Library {
// https://stackoverflow.com/a/52571774/4814697
// separate prefix with IV from the rest of encrypted data//separate prefix with IV from the rest of encrypted data
val encryptedPayload = Base64.getDecoder.decode(value)
val iv = new Array[Byte](cipher.getBlockSize)
val encryptedBytes = new Array[Byte](encryptedPayload.length - iv.length)
val rand: SecureRandom = new SecureRandom()

val iv: Array[Byte] = if (args("iv") == Val.Null) {
val newIV = new Array[Byte](cipher.getBlockSize)
rand.nextBytes(newIV)
newIV
} else {
args("iv").asInstanceOf[Val.Str].value.getBytes
}

val encryptedBytes = new Array[Byte](encryptedPayload.length - iv.length)

// populate iv with bytes:
System.arraycopy(encryptedPayload, 0, iv, 0, iv.length)

Expand Down
16 changes: 14 additions & 2 deletions src/test/java/com/datasonnet/CryptoTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.datasonnet;

/*-
* Copyright 2019-2020 the original author or authors.
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,7 +15,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;
Expand Down Expand Up @@ -185,5 +184,18 @@ void testEncryptDecrypt() {
String msg = e.getMessage();
assertTrue(msg != null && msg.contains("Caused by: java.security.InvalidKeyException: Invalid AES key length"));
}

//Encrypt / decrypt with provided IV
alg ="AES"; mode="CBC"; padding="PKCS5Padding";
mapper = new Mapper("ds.crypto.encrypt('Hello World', 'DataSonnet123456', '" + alg + "/" + mode + "/" + padding + "', 's9X([cZ{#W{(x6Y7')");
encrypted = mapper.transform("{}").replaceAll("\"", "");

mapper = new Mapper("ds.crypto.decrypt('" + encrypted + "', 'DataSonnet123456', '" + alg + "/" + mode + "/" + padding + "', 's9X([cZ{#W{(x6Y7')");
decrypted = mapper.transform("{}").replaceAll("\"", "");
assertEquals("Hello World", decrypted);

mapper = new Mapper("ds.crypto.decrypt('" + encrypted + "', 'DataSonnet123456', '" + alg + "/" + mode + "/" + padding + "')");
decrypted = mapper.transform("{}").replaceAll("\"", "");
assertEquals("Hello World", decrypted);
}
}

0 comments on commit 9a5aaf5

Please sign in to comment.