@@ -18,7 +18,7 @@ package com.datasonnet
18
18
19
19
import java .math .{BigDecimal , RoundingMode }
20
20
import java .net .URL
21
- import java .nio . charset .{ Charset , StandardCharsets }
21
+ import java .security . SecureRandom
22
22
import java .text .DecimalFormat
23
23
import java .time .format .DateTimeFormatter
24
24
import java .time .temporal .ChronoUnit
@@ -1066,68 +1066,61 @@ object DSLowercase extends Library {
1066
1066
},
1067
1067
1068
1068
/**
1069
- * Encrypts the value with specified algorithm, mode, and padding using the provided secret. Converts the encryption to a readable format with Base64
1070
- * Possible algorithms to use are AES, DES, and DESede. The provided secret must be of lengths 16 or 32, 8, and 24 respectively.
1071
- * All Algorithms only support NoPadding or PKCS5Padding
1069
+ * Encrypts the value with specified JDK Cipher Transformation using the provided secret. Converts the encryption
1070
+ * to a readable format with Base64
1072
1071
*
1073
1072
* @builtinParam value The message to be encrypted.
1074
1073
* @types [String]
1075
1074
* @builtinParam secret The secret used to encrypt the original messsage.
1076
1075
* @types [String]
1077
- * @builtinParam algorithm The algorithm used for the encryption.
1078
- * @types [String]
1079
- * @builtinParam mode The encryption mode to be used.
1080
- * @types [String]
1081
- * @builtinParam padding The encryption secret padding to be used
1076
+ * @builtinParam transformation The string that describes the operation (or set of operations) to be performed on
1077
+ * the given input, to produce some output. A transformation always includes the name of a cryptographic algorithm
1078
+ * (e.g., AES), and may be followed by a feedback mode and padding scheme. A transformation is of the form:
1079
+ * "algorithm/mode/padding" or "algorithm"
1082
1080
* @types [String]
1083
- *
1084
1081
* @builtinReturn Base64 String value of the encrypted message
1085
1082
* @types [String]
1086
- *
1087
- * @changed 0.7.1
1083
+ * @changed 0.7.2
1088
1084
*/
1089
- builtin0(" encrypt" , " value" , " secret" , " algorithm " , " mode " , " padding " ) {
1090
- (vals, ev,fs) =>
1091
- val valSeq = validate(vals, ev, fs, Array (StringRead , StringRead , StringRead , StringRead , StringRead ))
1085
+ builtin0[ Val ] (" encrypt" , " value" , " secret" , " transformation " ) {
1086
+ (vals, ev, fs) =>
1087
+ val valSeq = validate(vals, ev, fs, Array (StringRead , StringRead , StringRead ))
1092
1088
val value = valSeq(0 ).asInstanceOf [String ]
1093
1089
val secret = valSeq(1 ).asInstanceOf [String ]
1094
- val algorithm = valSeq(2 ).asInstanceOf [String ]
1095
- val mode = valSeq(3 ).asInstanceOf [String ]
1096
- val padding = valSeq(4 ).asInstanceOf [String ]
1097
- var ivSize : Int = 0
1090
+ val transformation = valSeq(2 ).asInstanceOf [String ]
1098
1091
1099
- if (! padding.equalsIgnoreCase(" NoPadding" ) && ! padding.equalsIgnoreCase(" PKCS5Padding" )){
1100
- {throw Error .Delegate (" Padding must be either: NoPadding or PKCS5Padding, got: " + padding)}
1101
- }
1092
+ val cipher = Cipher .getInstance(transformation)
1093
+ val transformTokens = transformation.split(" /" )
1102
1094
1103
- algorithm.toUpperCase() match {
1104
- case " AES" =>
1105
- ivSize = 16 ;
1106
- if (secret.length != 16 && secret.length != 32 )
1107
- {throw Error .Delegate (" Secret length must be 16 or 32 bytes, got: " + secret.length)}
1108
- case " DES" =>
1109
- ivSize = 8
1110
- if (secret.length != 8 )
1111
- {throw Error .Delegate (" Secret length must be 8 bytes, got: " + secret.length) }
1112
- case " DESEDE" =>
1113
- ivSize = 8
1114
- if (secret.length != 24 )
1115
- {throw Error .Delegate (" Secret length must be 24 bytes, got: " + secret.length) }
1116
- case i => throw Error .Delegate (" Expected algorithm to be one of AES, DES, DESede, or RSA. Got: " + i)
1117
- }
1095
+ // special case for ECB because of java.security.InvalidAlgorithmParameterException: ECB mode cannot use IV
1096
+ if (transformTokens.length >= 2 && " ECB" .equals(transformTokens(1 ))) {
1097
+ cipher.init(Cipher .ENCRYPT_MODE , new SecretKeySpec (secret.getBytes, transformTokens(0 ).toUpperCase))
1098
+ Val .Str (Base64 .getEncoder.encodeToString(cipher.doFinal(value.getBytes)))
1118
1099
1119
- var cipher : Cipher = null
1100
+ } else {
1101
+ // https://stackoverflow.com/a/52571774/4814697
1102
+ val rand : SecureRandom = new SecureRandom ()
1103
+ val iv = new Array [Byte ](cipher.getBlockSize)
1104
+ rand.nextBytes(iv)
1105
+
1106
+ cipher.init(Cipher .ENCRYPT_MODE ,
1107
+ new SecretKeySpec (secret.getBytes, transformTokens(0 ).toUpperCase),
1108
+ new IvParameterSpec (iv),
1109
+ rand)
1110
+
1111
+ // encrypted data:
1112
+ val encryptedBytes = cipher.doFinal(value.getBytes)
1113
+
1114
+ // append Initiation Vector as a prefix to use it during decryption:
1115
+ val combinedPayload = new Array [Byte ](iv.length + encryptedBytes.length)
1120
1116
1121
- mode.toUpperCase match {
1122
- case " CBC" =>
1123
- cipher = Cipher .getInstance(algorithm.toUpperCase + " /CBC/" + padding)
1124
- cipher.init(Cipher .ENCRYPT_MODE , new SecretKeySpec (secret.getBytes, algorithm.toUpperCase), new IvParameterSpec (new Array [Byte ](ivSize)))
1125
- case " ECB" =>
1126
- cipher = Cipher .getInstance(algorithm.toUpperCase + " /ECB/" + padding)
1127
- cipher.init(Cipher .ENCRYPT_MODE , new SecretKeySpec (secret.getBytes, algorithm.toUpperCase))
1128
- case i => throw Error .Delegate (" Expected mode to be either CBC or ECB, got: " + i)
1117
+ // populate payload with prefix IV and encrypted data
1118
+ System .arraycopy(iv, 0 , combinedPayload, 0 , iv.length)
1119
+ System .arraycopy(encryptedBytes, 0 , combinedPayload, iv.length, encryptedBytes.length)
1120
+
1121
+ Val .Str (Base64 .getEncoder.encodeToString(combinedPayload))
1129
1122
}
1130
- Val . Lazy ( Val . Str ( Base64 .getEncoder.encodeToString(cipher.doFinal(value.getBytes)))).force
1123
+
1131
1124
},
1132
1125
1133
1126
/**
@@ -1151,49 +1144,43 @@ object DSLowercase extends Library {
1151
1144
*
1152
1145
* @changed 0.7.1
1153
1146
*/
1154
- builtin0(" decrypt" , " value" , " secret" , " algorithm " , " mode " , " padding " ) {
1147
+ builtin0[ Val ] (" decrypt" , " value" , " secret" , " transformation " ) {
1155
1148
(vals, ev,fs) =>
1156
- val valSeq = validate(vals, ev, fs, Array (StringRead , StringRead , StringRead , StringRead , StringRead ))
1149
+ val valSeq = validate(vals, ev, fs, Array (StringRead , StringRead , StringRead ))
1157
1150
val value = valSeq(0 ).asInstanceOf [String ]
1158
1151
val secret = valSeq(1 ).asInstanceOf [String ]
1159
- val algorithm = valSeq(2 ).asInstanceOf [String ]
1160
- val mode = valSeq(3 ).asInstanceOf [String ]
1161
- val padding = valSeq(4 ).asInstanceOf [String ]
1162
- var ivSize : Int = 0
1152
+ val transformation = valSeq(2 ).asInstanceOf [String ]
1163
1153
1164
- if (! padding.equalsIgnoreCase(" NoPadding" ) && ! padding.equalsIgnoreCase(" PKCS5Padding" )){
1165
- {throw Error .Delegate (" Padding must be either: NoPadding or PKCS5Padding, got: " + padding)}
1166
- }
1154
+ val cipher = Cipher .getInstance(transformation)
1155
+ val transformTokens = transformation.split(" /" )
1167
1156
1168
- algorithm.toUpperCase() match {
1169
- case " AES " =>
1170
- ivSize = 16 ;
1171
- if (secret.length != 16 && secret.length != 32 )
1172
- { throw Error . Delegate ( " Secret length must be 16 or 32 bytes, got: " + secret.length)}
1173
- case " DES " =>
1174
- ivSize = 8
1175
- if (secret.length != 8 )
1176
- { throw Error . Delegate ( " Secret length must be 8 bytes, got: " + secret.length) }
1177
- case " DESEDE " =>
1178
- ivSize = 8
1179
- if (secret.length != 24 )
1180
- { throw Error . Delegate ( " Secret length must be 24 bytes, got: " + secret.length) }
1181
- case i => throw Error . Delegate ( " Expected algorithm to be one of AES, DES, DESede, or RSA. Got: " + i)
1182
- }
1157
+ // special case for ECB because of java.security.InvalidAlgorithmParameterException: ECB mode cannot use IV
1158
+ if (transformTokens.length >= 2 && " ECB " .equals(transformTokens( 1 ))) {
1159
+ cipher.init( Cipher . DECRYPT_MODE , new SecretKeySpec (secret.getBytes, transformTokens( 0 ).toUpperCase))
1160
+ Val . Str ( new String (cipher.doFinal( Base64 .getDecoder.decode(value))) )
1161
+
1162
+ } else {
1163
+ // https://stackoverflow.com/a/52571774/4814697
1164
+ // separate prefix with IV from the rest of encrypted data//separate prefix with IV from the rest of encrypted data
1165
+ val encryptedPayload = Base64 .getDecoder.decode(value)
1166
+ val iv = new Array [ Byte ](cipher.getBlockSize)
1167
+ val encryptedBytes = new Array [ Byte ](encryptedPayload.length - iv.length)
1168
+ val rand : SecureRandom = new SecureRandom ( )
1169
+
1170
+ // populate iv with bytes:
1171
+ System .arraycopy(encryptedPayload, 0 , iv, 0 , iv.length)
1183
1172
1184
- var cipher : Cipher = null
1173
+ // populate encryptedBytes with bytes:
1174
+ System .arraycopy(encryptedPayload, iv.length, encryptedBytes, 0 , encryptedBytes.length)
1185
1175
1186
- mode.toUpperCase match {
1187
- case " CBC" =>
1188
- cipher = Cipher .getInstance(algorithm.toUpperCase + " /CBC/" + padding)
1189
- cipher.init(Cipher .DECRYPT_MODE , new SecretKeySpec (secret.getBytes, algorithm.toUpperCase), new IvParameterSpec (new Array [Byte ](ivSize)))
1190
- case " ECB" =>
1191
- cipher = Cipher .getInstance(algorithm.toUpperCase + " /ECB/" + padding)
1192
- cipher.init(Cipher .DECRYPT_MODE , new SecretKeySpec (secret.getBytes, algorithm.toUpperCase))
1193
- case i => throw Error .Delegate (" Expected mode to be either CBC or ECB, got: " + i)
1176
+ cipher.init(Cipher .DECRYPT_MODE ,
1177
+ new SecretKeySpec (secret.getBytes, transformTokens(0 ).toUpperCase),
1178
+ new IvParameterSpec (iv),
1179
+ rand)
1180
+
1181
+ Val .Str (new String (cipher.doFinal(encryptedBytes)))
1194
1182
}
1195
- Val .Lazy (Val .Str (new String (cipher.doFinal(Base64 .getDecoder.decode(value)), Charset .forName(" UTF-8" )))).force
1196
- },
1183
+ }
1197
1184
),
1198
1185
1199
1186
" jsonpath" -> moduleFrom(
0 commit comments