From 616a68ca2bb0e3848d28d48cca11de43a36adc32 Mon Sep 17 00:00:00 2001 From: Yuanchun Shen Date: Tue, 22 Apr 2025 13:41:00 +0800 Subject: [PATCH 1/7] Implement cryptographic functions Signed-off-by: Yuanchun Shen --- .../function/BuiltinFunctionName.java | 5 ++ .../function/PPLBuiltinOperators.java | 5 ++ .../expression/function/PPLFuncImpTable.java | 6 ++ .../udf/crypto/CryptographicFunction.java | 71 +++++++++++++++++ .../function/udf/crypto/Sha2Function.java | 76 +++++++++++++++++++ .../CalcitePPLCryptographicFunctionIT.java | 62 +++++++++++++++ 6 files changed, 225 insertions(+) create mode 100644 core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/CryptographicFunction.java create mode 100644 core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/Sha2Function.java create mode 100644 integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java diff --git a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java index 432eb09d49d..c5804ee1c71 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java @@ -136,6 +136,11 @@ public enum BuiltinFunctionName { /** IP Functions. */ CIDRMATCH(FunctionName.of("cidrmatch")), + /** Cryptographic Functions. */ + MD5(FunctionName.of("md5")), + SHA1(FunctionName.of("sha1")), + SHA2(FunctionName.of("sha2")), + /** Arithmetic Operators. */ ADD(FunctionName.of("+")), ADDFUNCTION(FunctionName.of("add")), diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java index e1ff69660b1..bc4691227f8 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java @@ -16,11 +16,16 @@ import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable; import org.apache.calcite.util.BuiltInMethod; +import org.opensearch.sql.expression.function.udf.crypto.CryptographicFunction; +import org.opensearch.sql.expression.function.udf.crypto.Sha2Function; /** Defines functions and operators that are implemented only by PPL */ public class PPLBuiltinOperators extends ReflectiveSqlOperatorTable { public static final SqlOperator SPAN = new SpanFunctionImpl().toUDF("SPAN"); + public static final SqlOperator MD5 = CryptographicFunction.md5().toUDF("MD5"); + public static final SqlOperator SHA1 = CryptographicFunction.sha1().toUDF("SHA-1"); + public static final SqlOperator SHA2 = new Sha2Function().toUDF("SHA-256"); /** * Invoking an implementor registered in {@link RexImpTable}, need to use reflection since they're diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java index c5bd3db4665..dbe4b977ba6 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java @@ -41,6 +41,7 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.LOWER; import static org.opensearch.sql.expression.function.BuiltinFunctionName.LTE; import static org.opensearch.sql.expression.function.BuiltinFunctionName.LTRIM; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.MD5; import static org.opensearch.sql.expression.function.BuiltinFunctionName.MULTIPLY; import static org.opensearch.sql.expression.function.BuiltinFunctionName.NOT; import static org.opensearch.sql.expression.function.BuiltinFunctionName.NOTEQUAL; @@ -56,6 +57,8 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.RIGHT; import static org.opensearch.sql.expression.function.BuiltinFunctionName.ROUND; import static org.opensearch.sql.expression.function.BuiltinFunctionName.RTRIM; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.SHA1; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.SHA2; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SIGN; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SIN; import static org.opensearch.sql.expression.function.BuiltinFunctionName.SPAN; @@ -271,6 +274,9 @@ void populate() { // Register PPL UDF operator registerOperator(SPAN, PPLBuiltinOperators.SPAN); + registerOperator(MD5, PPLBuiltinOperators.MD5); + registerOperator(SHA1, PPLBuiltinOperators.SHA1); + registerOperator(SHA2, PPLBuiltinOperators.SHA2); // Register implementation. // Note, make the implementation an individual class if too complex. diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/CryptographicFunction.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/CryptographicFunction.java new file mode 100644 index 00000000000..bf425265b3b --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/CryptographicFunction.java @@ -0,0 +1,71 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.expression.function.udf.crypto; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import org.apache.calcite.adapter.enumerable.NotNullImplementor; +import org.apache.calcite.adapter.enumerable.NullPolicy; +import org.apache.calcite.adapter.enumerable.RexToLixTranslator; +import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.linq4j.tree.Expressions; +import org.apache.calcite.rex.RexCall; +import org.apache.calcite.sql.type.ReturnTypes; +import org.apache.calcite.sql.type.SqlReturnTypeInference; +import org.apache.calcite.sql.type.SqlTypeTransforms; +import org.opensearch.sql.expression.function.ImplementorUDF; + +public class CryptographicFunction extends ImplementorUDF { + private CryptographicFunction(String algorithm, NullPolicy nullPolicy) { + super(new CryptographicImplementor(algorithm), nullPolicy); + } + + public static CryptographicFunction md5() { + return new CryptographicFunction("MD5", NullPolicy.ARG0); + } + + public static CryptographicFunction sha1() { + return new CryptographicFunction("SHA-1", NullPolicy.ARG0); + } + + @Override + public SqlReturnTypeInference getReturnTypeInference() { + return ReturnTypes.VARCHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE); + } + + public static class CryptographicImplementor implements NotNullImplementor { + private final MessageDigest digest; + + public CryptographicImplementor(String algorithm) { + try { + this.digest = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(algorithm + " is not supported"); + } + } + + @Override + public Expression implement( + RexToLixTranslator translator, RexCall call, List translatedOperands) { + return Expressions.call( + Expressions.parameter(this.getClass(), "this"), "getDigest", translatedOperands); + } + + public String getDigest(String input) { + byte[] hash = digest.digest(input.getBytes()); + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } + } +} diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/Sha2Function.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/Sha2Function.java new file mode 100644 index 00000000000..2f485086292 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/Sha2Function.java @@ -0,0 +1,76 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.expression.function.udf.crypto; + +import java.security.MessageDigest; +import java.util.List; +import java.util.Map; +import org.apache.calcite.adapter.enumerable.NotNullImplementor; +import org.apache.calcite.adapter.enumerable.NullPolicy; +import org.apache.calcite.adapter.enumerable.RexToLixTranslator; +import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.linq4j.tree.Expressions; +import org.apache.calcite.rex.RexCall; +import org.apache.calcite.sql.type.ReturnTypes; +import org.apache.calcite.sql.type.SqlReturnTypeInference; +import org.apache.calcite.sql.type.SqlTypeTransforms; +import org.opensearch.sql.expression.function.ImplementorUDF; + +public class Sha2Function extends ImplementorUDF { + public Sha2Function() { + super(new Sha2Implementor(), NullPolicy.ANY); + } + + @Override + public SqlReturnTypeInference getReturnTypeInference() { + return ReturnTypes.VARCHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE); + } + + public static class Sha2Implementor implements NotNullImplementor { + private final Map digests; + + public Sha2Implementor() { + try { + digests = + Map.of( + 224, + MessageDigest.getInstance("SHA-224"), + 256, + MessageDigest.getInstance("SHA-256"), + 384, + MessageDigest.getInstance("SHA-384"), + 512, + MessageDigest.getInstance("SHA-512")); + } catch (Exception e) { + throw new IllegalArgumentException("SHA-2 algorithms are not supported", e); + } + } + + @Override + public Expression implement( + RexToLixTranslator translator, RexCall call, List translatedOperands) { + return Expressions.call( + Expressions.parameter(this.getClass(), "this"), "getDigest", translatedOperands); + } + + public String getDigest(String input, int algorithm) { + MessageDigest digest = digests.get(algorithm); + if (digest == null) { + throw new IllegalArgumentException("Unsupported SHA-2 algorithm: " + algorithm); + } + byte[] hash = digest.digest(input.getBytes()); + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } + } +} diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java new file mode 100644 index 00000000000..3edcc8decbe --- /dev/null +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.calcite.standalone; + +import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_PEOPLE; +import static org.opensearch.sql.util.MatcherUtils.rows; +import static org.opensearch.sql.util.MatcherUtils.schema; +import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; +import static org.opensearch.sql.util.MatcherUtils.verifySchema; + +import java.io.IOException; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; + +public class CalcitePPLCryptographicFunctionIT extends CalcitePPLIntegTestCase { + @Override + public void init() throws IOException { + super.init(); + loadIndex(Index.PEOPLE); + } + + @Test + public void testMd5() { + JSONObject actual = + executeQuery( + String.format( + "source=%s | head 1 | eval hello = MD5('hello') | fields hello", + TEST_INDEX_PEOPLE)); + verifySchema(actual, schema("hello", "string")); + verifyDataRows(actual, rows("5d41402abc4b2a76b9719d911017c592")); + } + + @Test + public void testSha1() { + JSONObject actual = + executeQuery( + String.format( + "source=%s | head 1 | eval hello = SHA1('hello') | fields hello", + TEST_INDEX_PEOPLE)); + verifySchema(actual, schema("hello", "string")); + verifyDataRows(actual, rows("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d")); + } + + @Test + public void testSha2() { + JSONObject actual = + executeQuery( + String.format( + "source=%s | head 1 | eval sha256 = SHA2('hello',256), sha512 = SHA2('hello',512) " + + " | fields sha256, sha512", + TEST_INDEX_PEOPLE)); + verifySchema(actual, schema("sha256", "string"), schema("sha512", "string")); + verifyDataRows( + actual, + rows( + "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", + "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043")); + } +} From 33c390fb6391f721b8bf41238471e747198bd575 Mon Sep 17 00:00:00 2001 From: Yuanchun Shen Date: Tue, 22 Apr 2025 16:21:00 +0800 Subject: [PATCH 2/7] Implement cryptographic UDFs Signed-off-by: Yuanchun Shen --- .../function/PPLBuiltinOperators.java | 7 +- .../function/udf/CryptographicFunction.java | 135 ++++++++++++++++++ .../udf/crypto/CryptographicFunction.java | 71 --------- .../function/udf/crypto/Sha2Function.java | 76 ---------- .../CalcitePPLCryptographicFunctionIT.java | 14 ++ ppl/src/main/antlr/OpenSearchPPLLexer.g4 | 5 + ppl/src/main/antlr/OpenSearchPPLParser.g4 | 7 + 7 files changed, 164 insertions(+), 151 deletions(-) create mode 100644 core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java delete mode 100644 core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/CryptographicFunction.java delete mode 100644 core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/Sha2Function.java diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java index bc4691227f8..1513408bd2e 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java @@ -16,16 +16,15 @@ import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable; import org.apache.calcite.util.BuiltInMethod; -import org.opensearch.sql.expression.function.udf.crypto.CryptographicFunction; -import org.opensearch.sql.expression.function.udf.crypto.Sha2Function; +import org.opensearch.sql.expression.function.udf.CryptographicFunction; /** Defines functions and operators that are implemented only by PPL */ public class PPLBuiltinOperators extends ReflectiveSqlOperatorTable { public static final SqlOperator SPAN = new SpanFunctionImpl().toUDF("SPAN"); public static final SqlOperator MD5 = CryptographicFunction.md5().toUDF("MD5"); - public static final SqlOperator SHA1 = CryptographicFunction.sha1().toUDF("SHA-1"); - public static final SqlOperator SHA2 = new Sha2Function().toUDF("SHA-256"); + public static final SqlOperator SHA1 = CryptographicFunction.sha1().toUDF("SHA1"); + public static final SqlOperator SHA2 = CryptographicFunction.sha2().toUDF("SHA2"); /** * Invoking an implementor registered in {@link RexImpTable}, need to use reflection since they're diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java new file mode 100644 index 00000000000..dd7fea347a2 --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java @@ -0,0 +1,135 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.expression.function.udf; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Map; +import org.apache.calcite.adapter.enumerable.NotNullImplementor; +import org.apache.calcite.adapter.enumerable.NullPolicy; +import org.apache.calcite.adapter.enumerable.RexToLixTranslator; +import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.linq4j.tree.Expressions; +import org.apache.calcite.rex.RexCall; +import org.apache.calcite.sql.type.ReturnTypes; +import org.apache.calcite.sql.type.SqlReturnTypeInference; +import org.apache.calcite.sql.type.SqlTypeTransforms; +import org.opensearch.sql.expression.function.ImplementorUDF; + +public class CryptographicFunction extends ImplementorUDF { + private CryptographicFunction(NotNullImplementor implementor, NullPolicy nullPolicy) { + super(implementor, nullPolicy); + } + + public static CryptographicFunction md5() { + return new CryptographicFunction(new Md5Implementor(), NullPolicy.ARG0); + } + + public static CryptographicFunction sha1() { + return new CryptographicFunction(new Sha1Implementor(), NullPolicy.ARG0); + } + + public static CryptographicFunction sha2() { + return new CryptographicFunction(new Sha2Implementor(), NullPolicy.ANY); + } + + @Override + public SqlReturnTypeInference getReturnTypeInference() { + return ReturnTypes.VARCHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE); + } + + private static String getDigest(MessageDigest digest, String input) { + byte[] hash = digest.digest(input.getBytes()); + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } + + public static class Md5Implementor implements NotNullImplementor { + private static final MessageDigest MD5_DIGEST; + + static { + try { + MD5_DIGEST = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5 algorithm not found", e); + } + } + + @Override + public Expression implement( + RexToLixTranslator translator, RexCall call, List translatedOperands) { + return Expressions.call(Md5Implementor.class, "getDigest", translatedOperands); + } + + public static String getDigest(String input) { + return CryptographicFunction.getDigest(MD5_DIGEST, input); + } + } + + public static class Sha1Implementor implements NotNullImplementor { + private static final MessageDigest SHA1_DIGEST; + + static { + try { + SHA1_DIGEST = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-1 algorithm not found", e); + } + } + + @Override + public Expression implement( + RexToLixTranslator translator, RexCall call, List translatedOperands) { + return Expressions.call(Sha1Implementor.class, "getDigest", translatedOperands); + } + + public static String getDigest(String input) { + return CryptographicFunction.getDigest(SHA1_DIGEST, input); + } + } + + public static class Sha2Implementor implements NotNullImplementor { + private static final Map digests; + + static { + try { + digests = + Map.of( + 224, + MessageDigest.getInstance("SHA-224"), + 256, + MessageDigest.getInstance("SHA-256"), + 384, + MessageDigest.getInstance("SHA-384"), + 512, + MessageDigest.getInstance("SHA-512")); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + @Override + public Expression implement( + RexToLixTranslator translator, RexCall call, List translatedOperands) { + return Expressions.call(Sha2Implementor.class, "getDigest", translatedOperands); + } + + public static String getDigest(String input, int algorithm) { + if (!digests.containsKey(algorithm)) { + throw new IllegalArgumentException("Unsupported SHA2 algorithm: " + algorithm); + } + return CryptographicFunction.getDigest(digests.get(algorithm), input); + } + } +} diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/CryptographicFunction.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/CryptographicFunction.java deleted file mode 100644 index bf425265b3b..00000000000 --- a/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/CryptographicFunction.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.sql.expression.function.udf.crypto; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import org.apache.calcite.adapter.enumerable.NotNullImplementor; -import org.apache.calcite.adapter.enumerable.NullPolicy; -import org.apache.calcite.adapter.enumerable.RexToLixTranslator; -import org.apache.calcite.linq4j.tree.Expression; -import org.apache.calcite.linq4j.tree.Expressions; -import org.apache.calcite.rex.RexCall; -import org.apache.calcite.sql.type.ReturnTypes; -import org.apache.calcite.sql.type.SqlReturnTypeInference; -import org.apache.calcite.sql.type.SqlTypeTransforms; -import org.opensearch.sql.expression.function.ImplementorUDF; - -public class CryptographicFunction extends ImplementorUDF { - private CryptographicFunction(String algorithm, NullPolicy nullPolicy) { - super(new CryptographicImplementor(algorithm), nullPolicy); - } - - public static CryptographicFunction md5() { - return new CryptographicFunction("MD5", NullPolicy.ARG0); - } - - public static CryptographicFunction sha1() { - return new CryptographicFunction("SHA-1", NullPolicy.ARG0); - } - - @Override - public SqlReturnTypeInference getReturnTypeInference() { - return ReturnTypes.VARCHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE); - } - - public static class CryptographicImplementor implements NotNullImplementor { - private final MessageDigest digest; - - public CryptographicImplementor(String algorithm) { - try { - this.digest = MessageDigest.getInstance(algorithm); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException(algorithm + " is not supported"); - } - } - - @Override - public Expression implement( - RexToLixTranslator translator, RexCall call, List translatedOperands) { - return Expressions.call( - Expressions.parameter(this.getClass(), "this"), "getDigest", translatedOperands); - } - - public String getDigest(String input) { - byte[] hash = digest.digest(input.getBytes()); - StringBuilder hexString = new StringBuilder(); - for (byte b : hash) { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); - } - } -} diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/Sha2Function.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/Sha2Function.java deleted file mode 100644 index 2f485086292..00000000000 --- a/core/src/main/java/org/opensearch/sql/expression/function/udf/crypto/Sha2Function.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.sql.expression.function.udf.crypto; - -import java.security.MessageDigest; -import java.util.List; -import java.util.Map; -import org.apache.calcite.adapter.enumerable.NotNullImplementor; -import org.apache.calcite.adapter.enumerable.NullPolicy; -import org.apache.calcite.adapter.enumerable.RexToLixTranslator; -import org.apache.calcite.linq4j.tree.Expression; -import org.apache.calcite.linq4j.tree.Expressions; -import org.apache.calcite.rex.RexCall; -import org.apache.calcite.sql.type.ReturnTypes; -import org.apache.calcite.sql.type.SqlReturnTypeInference; -import org.apache.calcite.sql.type.SqlTypeTransforms; -import org.opensearch.sql.expression.function.ImplementorUDF; - -public class Sha2Function extends ImplementorUDF { - public Sha2Function() { - super(new Sha2Implementor(), NullPolicy.ANY); - } - - @Override - public SqlReturnTypeInference getReturnTypeInference() { - return ReturnTypes.VARCHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE); - } - - public static class Sha2Implementor implements NotNullImplementor { - private final Map digests; - - public Sha2Implementor() { - try { - digests = - Map.of( - 224, - MessageDigest.getInstance("SHA-224"), - 256, - MessageDigest.getInstance("SHA-256"), - 384, - MessageDigest.getInstance("SHA-384"), - 512, - MessageDigest.getInstance("SHA-512")); - } catch (Exception e) { - throw new IllegalArgumentException("SHA-2 algorithms are not supported", e); - } - } - - @Override - public Expression implement( - RexToLixTranslator translator, RexCall call, List translatedOperands) { - return Expressions.call( - Expressions.parameter(this.getClass(), "this"), "getDigest", translatedOperands); - } - - public String getDigest(String input, int algorithm) { - MessageDigest digest = digests.get(algorithm); - if (digest == null) { - throw new IllegalArgumentException("Unsupported SHA-2 algorithm: " + algorithm); - } - byte[] hash = digest.digest(input.getBytes()); - StringBuilder hexString = new StringBuilder(); - for (byte b : hash) { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); - } - } -} diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java index 3edcc8decbe..f157ede31a8 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java @@ -9,6 +9,7 @@ import static org.opensearch.sql.util.MatcherUtils.rows; import static org.opensearch.sql.util.MatcherUtils.schema; import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; +import static org.opensearch.sql.util.MatcherUtils.verifyErrorMessageContains; import static org.opensearch.sql.util.MatcherUtils.verifySchema; import java.io.IOException; @@ -59,4 +60,17 @@ public void testSha2() { "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043")); } + + @Test + public void testSha2WrongAlgorithmShouldThrow() { + Throwable e = + assertThrows( + IllegalArgumentException.class, + () -> + executeQuery( + String.format( + "source=%s | head 1 | eval sha100 = SHA2('hello', 100) | fields sha100", + TEST_INDEX_PEOPLE))); + verifyErrorMessageContains(e, "Unsupported SHA2 algorithm"); + } } diff --git a/ppl/src/main/antlr/OpenSearchPPLLexer.g4 b/ppl/src/main/antlr/OpenSearchPPLLexer.g4 index 00b5509b292..a710c65188e 100644 --- a/ppl/src/main/antlr/OpenSearchPPLLexer.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLLexer.g4 @@ -265,6 +265,11 @@ RADIANS: 'RADIANS'; SIN: 'SIN'; TAN: 'TAN'; +// CRYPTOGRAPHIC FUNCTIONS +MD5: 'MD5'; +SHA1: 'SHA1'; +SHA2: 'SHA2'; + // DATE AND TIME FUNCTIONS ADDDATE: 'ADDDATE'; ADDTIME: 'ADDTIME'; diff --git a/ppl/src/main/antlr/OpenSearchPPLParser.g4 b/ppl/src/main/antlr/OpenSearchPPLParser.g4 index 8a8343afe9e..bd9be26726f 100644 --- a/ppl/src/main/antlr/OpenSearchPPLParser.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLParser.g4 @@ -528,6 +528,7 @@ evalFunctionName | flowControlFunctionName | systemFunctionName | positionFunctionName + | cryptographicFunctionName | jsonFunctionName | geoipFunctionName ; @@ -648,6 +649,12 @@ trigonometricFunctionName | TAN ; +cryptographicFunctionName + : MD5 + | SHA1 + | SHA2 + ; + dateTimeFunctionName : ADDDATE | ADDTIME From f56d5ff64ea367d6149a9f40a8aec07f32b22b12 Mon Sep 17 00:00:00 2001 From: Yuanchun Shen Date: Wed, 23 Apr 2025 15:17:26 +0800 Subject: [PATCH 3/7] Wrap message digests with ThreadLocal to enforce thread safety Signed-off-by: Yuanchun Shen --- .../function/udf/CryptographicFunction.java | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java index dd7fea347a2..1b9aeb7d2fa 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java @@ -18,6 +18,7 @@ import org.apache.calcite.sql.type.ReturnTypes; import org.apache.calcite.sql.type.SqlReturnTypeInference; import org.apache.calcite.sql.type.SqlTypeTransforms; +import org.apache.commons.codec.digest.DigestUtils; import org.opensearch.sql.expression.function.ImplementorUDF; public class CryptographicFunction extends ImplementorUDF { @@ -56,14 +57,10 @@ private static String getDigest(MessageDigest digest, String input) { } public static class Md5Implementor implements NotNullImplementor { - private static final MessageDigest MD5_DIGEST; + private static final ThreadLocal MD5_DIGEST; static { - try { - MD5_DIGEST = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("MD5 algorithm not found", e); - } + MD5_DIGEST = ThreadLocal.withInitial(DigestUtils::getMd5Digest); } @Override @@ -73,19 +70,15 @@ public Expression implement( } public static String getDigest(String input) { - return CryptographicFunction.getDigest(MD5_DIGEST, input); + return CryptographicFunction.getDigest(MD5_DIGEST.get(), input); } } public static class Sha1Implementor implements NotNullImplementor { - private static final MessageDigest SHA1_DIGEST; + private static final ThreadLocal SHA1_DIGEST; static { - try { - SHA1_DIGEST = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("SHA-1 algorithm not found", e); - } + SHA1_DIGEST = ThreadLocal.withInitial(DigestUtils::getSha1Digest); } @Override @@ -95,28 +88,31 @@ public Expression implement( } public static String getDigest(String input) { - return CryptographicFunction.getDigest(SHA1_DIGEST, input); + return CryptographicFunction.getDigest(SHA1_DIGEST.get(), input); } } public static class Sha2Implementor implements NotNullImplementor { - private static final Map digests; + private static final ThreadLocal> digests; static { - try { - digests = - Map.of( - 224, - MessageDigest.getInstance("SHA-224"), - 256, - MessageDigest.getInstance("SHA-256"), - 384, - MessageDigest.getInstance("SHA-384"), - 512, - MessageDigest.getInstance("SHA-512")); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } + digests = + ThreadLocal.withInitial( + () -> { + try { + return Map.of( + 224, + MessageDigest.getInstance("SHA-224"), + 256, + DigestUtils.getSha256Digest(), + 384, + DigestUtils.getSha384Digest(), + 512, + DigestUtils.getSha512Digest()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + }); } @Override @@ -126,10 +122,10 @@ public Expression implement( } public static String getDigest(String input, int algorithm) { - if (!digests.containsKey(algorithm)) { + if (!digests.get().containsKey(algorithm)) { throw new IllegalArgumentException("Unsupported SHA2 algorithm: " + algorithm); } - return CryptographicFunction.getDigest(digests.get(algorithm), input); + return CryptographicFunction.getDigest(digests.get().get(algorithm), input); } } } From 607dab452d6af673164932521146e234293885e8 Mon Sep 17 00:00:00 2001 From: Yuanchun Shen Date: Wed, 23 Apr 2025 16:01:15 +0800 Subject: [PATCH 4/7] Create documentation for cryptographic hash PPL functions Signed-off-by: Yuanchun Shen --- docs/user/ppl/functions/cryptographic.rst | 85 +++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 docs/user/ppl/functions/cryptographic.rst diff --git a/docs/user/ppl/functions/cryptographic.rst b/docs/user/ppl/functions/cryptographic.rst new file mode 100644 index 00000000000..496d7673835 --- /dev/null +++ b/docs/user/ppl/functions/cryptographic.rst @@ -0,0 +1,85 @@ +=========================== +PPL Cryptographic Functions +=========================== + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 1 + +MD5 +--- + +Description +>>>>>>>>>>> + + +Usage: ``md5(str)`` calculates the MD5 digest and returns the value as a 32 character hex string. + +Argument type: STRING + +Return type: STRING + +Example:: + + os> source=people | eval `MD5('hello')` = MD5('hello') | fields `MD5('hello')` + fetched rows / total rows = 1/1 + +----------------------------------+ + | MD5('hello') | + |----------------------------------| + | 5d41402abc4b2a76b9719d911017c592 | + +----------------------------------+ + +SHA1 +---- + +Description +>>>>>>>>>>> + +Usage: ``sha1(str)`` returns the hex string result of SHA-1. + +Argument type: STRING + +Return type: STRING + +Example:: + + os> source=people | eval `SHA1('hello')` = SHA1('hello') | fields `SHA1('hello')` + fetched rows / total rows = 1/1 + +------------------------------------------+ + | SHA1('hello') | + |------------------------------------------| + | aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d | + +------------------------------------------+ + +SHA2 +---- + +Description +>>>>>>>>>>> + +Usage: ``sha2(str, numBits)`` returns the hex string result of SHA-2 family of hash functions (SHA-224, SHA-256, SHA-384, and SHA-512). +The numBits indicates the desired bit length of the result, which must have a value of 224, 256, 384, or 512. + +Argument type: STRING, INTEGER + +Return type: STRING + +Example:: + + os> source=people | eval `SHA2('hello',256)` = SHA2('hello',256) | fields `SHA2('hello',256)` + fetched rows / total rows = 1/1 + +------------------------------------------------------------------+ + | SHA2('hello',256) | + |------------------------------------------------------------------| + | 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 | + +------------------------------------------------------------------+ + + os> source=people | eval `SHA2('hello',512)` = SHA2('hello',512) | fields `SHA2('hello',512)` + fetched rows / total rows = 1/1 + +----------------------------------------------------------------------------------------------------------------------------------+ + | SHA2('hello',512) | + |----------------------------------------------------------------------------------------------------------------------------------| + | 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043 | + +----------------------------------------------------------------------------------------------------------------------------------+ From abdcc9951df98fd1a72a9e0d4db563066fa7b282 Mon Sep 17 00:00:00 2001 From: Yuanchun Shen Date: Thu, 24 Apr 2025 11:08:08 +0800 Subject: [PATCH 5/7] Use calcite implementations of md5 and sha1 Signed-off-by: Yuanchun Shen --- .../function/PPLBuiltinOperators.java | 2 - .../expression/function/PPLFuncImpTable.java | 4 +- .../function/udf/CryptographicFunction.java | 72 ++++--------------- 3 files changed, 16 insertions(+), 62 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java index 1513408bd2e..a80f776d5a0 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java @@ -22,8 +22,6 @@ public class PPLBuiltinOperators extends ReflectiveSqlOperatorTable { public static final SqlOperator SPAN = new SpanFunctionImpl().toUDF("SPAN"); - public static final SqlOperator MD5 = CryptographicFunction.md5().toUDF("MD5"); - public static final SqlOperator SHA1 = CryptographicFunction.sha1().toUDF("SHA1"); public static final SqlOperator SHA2 = CryptographicFunction.sha2().toUDF("SHA2"); /** diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java index dbe4b977ba6..8a4e53f64cd 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java @@ -271,11 +271,11 @@ void populate() { registerOperator(RIGHT, SqlLibraryOperators.RIGHT); registerOperator(LEFT, SqlLibraryOperators.LEFT); registerOperator(LOG2, SqlLibraryOperators.LOG2); + registerOperator(MD5, SqlLibraryOperators.MD5); + registerOperator(SHA1, SqlLibraryOperators.SHA1); // Register PPL UDF operator registerOperator(SPAN, PPLBuiltinOperators.SPAN); - registerOperator(MD5, PPLBuiltinOperators.MD5); - registerOperator(SHA1, PPLBuiltinOperators.SHA1); registerOperator(SHA2, PPLBuiltinOperators.SHA2); // Register implementation. diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java index 1b9aeb7d2fa..136189709ce 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java @@ -26,14 +26,6 @@ private CryptographicFunction(NotNullImplementor implementor, NullPolicy nullPol super(implementor, nullPolicy); } - public static CryptographicFunction md5() { - return new CryptographicFunction(new Md5Implementor(), NullPolicy.ARG0); - } - - public static CryptographicFunction sha1() { - return new CryptographicFunction(new Sha1Implementor(), NullPolicy.ARG0); - } - public static CryptographicFunction sha2() { return new CryptographicFunction(new Sha2Implementor(), NullPolicy.ANY); } @@ -43,55 +35,6 @@ public SqlReturnTypeInference getReturnTypeInference() { return ReturnTypes.VARCHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE); } - private static String getDigest(MessageDigest digest, String input) { - byte[] hash = digest.digest(input.getBytes()); - StringBuilder hexString = new StringBuilder(); - for (byte b : hash) { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); - } - - public static class Md5Implementor implements NotNullImplementor { - private static final ThreadLocal MD5_DIGEST; - - static { - MD5_DIGEST = ThreadLocal.withInitial(DigestUtils::getMd5Digest); - } - - @Override - public Expression implement( - RexToLixTranslator translator, RexCall call, List translatedOperands) { - return Expressions.call(Md5Implementor.class, "getDigest", translatedOperands); - } - - public static String getDigest(String input) { - return CryptographicFunction.getDigest(MD5_DIGEST.get(), input); - } - } - - public static class Sha1Implementor implements NotNullImplementor { - private static final ThreadLocal SHA1_DIGEST; - - static { - SHA1_DIGEST = ThreadLocal.withInitial(DigestUtils::getSha1Digest); - } - - @Override - public Expression implement( - RexToLixTranslator translator, RexCall call, List translatedOperands) { - return Expressions.call(Sha1Implementor.class, "getDigest", translatedOperands); - } - - public static String getDigest(String input) { - return CryptographicFunction.getDigest(SHA1_DIGEST.get(), input); - } - } - public static class Sha2Implementor implements NotNullImplementor { private static final ThreadLocal> digests; @@ -125,7 +68,20 @@ public static String getDigest(String input, int algorithm) { if (!digests.get().containsKey(algorithm)) { throw new IllegalArgumentException("Unsupported SHA2 algorithm: " + algorithm); } - return CryptographicFunction.getDigest(digests.get().get(algorithm), input); + return getDigest(digests.get().get(algorithm), input); + } + + private static String getDigest(MessageDigest digest, String input) { + byte[] hash = digest.digest(input.getBytes()); + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); } } } From 629cb34739e1cf3150d727c0b260646cf0d2fa3f Mon Sep 17 00:00:00 2001 From: Yuanchun Shen Date: Thu, 24 Apr 2025 13:26:06 +0800 Subject: [PATCH 6/7] Test field values in cryptographic UDFs Signed-off-by: Yuanchun Shen --- .../CalcitePPLCryptographicFunctionIT.java | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java index f157ede31a8..6e30d2321da 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/standalone/CalcitePPLCryptographicFunctionIT.java @@ -5,7 +5,7 @@ package org.opensearch.sql.calcite.standalone; -import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_PEOPLE; +import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_STATE_COUNTRY; import static org.opensearch.sql.util.MatcherUtils.rows; import static org.opensearch.sql.util.MatcherUtils.schema; import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; @@ -20,7 +20,7 @@ public class CalcitePPLCryptographicFunctionIT extends CalcitePPLIntegTestCase { @Override public void init() throws IOException { super.init(); - loadIndex(Index.PEOPLE); + loadIndex(Index.STATE_COUNTRY); } @Test @@ -28,10 +28,12 @@ public void testMd5() { JSONObject actual = executeQuery( String.format( - "source=%s | head 1 | eval hello = MD5('hello') | fields hello", - TEST_INDEX_PEOPLE)); - verifySchema(actual, schema("hello", "string")); - verifyDataRows(actual, rows("5d41402abc4b2a76b9719d911017c592")); + "source=%s | where name = 'Jake' | eval hello = MD5('hello'), california =" + + " md5(state) | fields hello, california", + TEST_INDEX_STATE_COUNTRY)); + verifySchema(actual, schema("hello", "string"), schema("california", "string")); + verifyDataRows( + actual, rows("5d41402abc4b2a76b9719d911017c592", "356779a9a1696714480f57fa3fb66d4c")); } @Test @@ -39,10 +41,15 @@ public void testSha1() { JSONObject actual = executeQuery( String.format( - "source=%s | head 1 | eval hello = SHA1('hello') | fields hello", - TEST_INDEX_PEOPLE)); - verifySchema(actual, schema("hello", "string")); - verifyDataRows(actual, rows("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d")); + "source=%s | where name = 'John' | eval hello = SHA1('hello'), ontario =" + + " SHA1(state) | fields hello, ontario", + TEST_INDEX_STATE_COUNTRY)); + verifySchema(actual, schema("hello", "string"), schema("ontario", "string")); + verifyDataRows( + actual, + rows( + "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d", + "f9f742e1f653a74c4cd78d7ea283b5556539b96b")); } @Test @@ -50,15 +57,23 @@ public void testSha2() { JSONObject actual = executeQuery( String.format( - "source=%s | head 1 | eval sha256 = SHA2('hello',256), sha512 = SHA2('hello',512) " - + " | fields sha256, sha512", - TEST_INDEX_PEOPLE)); - verifySchema(actual, schema("sha256", "string"), schema("sha512", "string")); + "source=%s | where name = 'Jane' | eval sha256 = SHA2('hello',256), sha512 =" + + " SHA2('hello',512), sha224 = SHA2(country, 224), sha384 = SHA2(country, 384)" + + " | fields sha256, sha512, sha224, sha384", + TEST_INDEX_STATE_COUNTRY)); + verifySchema( + actual, + schema("sha256", "string"), + schema("sha512", "string"), + schema("sha224", "string"), + schema("sha384", "string")); verifyDataRows( actual, rows( "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", - "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043")); + "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043", + "c16f747ca3d2e267c76e7355429fb1583268d966887f237b8e1605c7", + "de2abcb28b87d681830f3af25cd8dde7fdc2a4da9dcfde60b371fd2378a70ac39cef3e104bbe09aecda022aee7b4bf59")); } @Test @@ -70,7 +85,7 @@ public void testSha2WrongAlgorithmShouldThrow() { executeQuery( String.format( "source=%s | head 1 | eval sha100 = SHA2('hello', 100) | fields sha100", - TEST_INDEX_PEOPLE))); + TEST_INDEX_STATE_COUNTRY))); verifyErrorMessageContains(e, "Unsupported SHA2 algorithm"); } } From 0d24c3174862edc78a8e1cde14cca5a6f75f3d86 Mon Sep 17 00:00:00 2001 From: Yuanchun Shen Date: Mon, 12 May 2025 11:27:54 +0800 Subject: [PATCH 7/7] Refactor: update sha2 implementations with DigestUtils Signed-off-by: Yuanchun Shen --- .../function/udf/CryptographicFunction.java | 55 +++++-------------- 1 file changed, 13 insertions(+), 42 deletions(-) diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java index 136189709ce..f73b9efee30 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/udf/CryptographicFunction.java @@ -5,10 +5,7 @@ package org.opensearch.sql.expression.function.udf; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.List; -import java.util.Map; import org.apache.calcite.adapter.enumerable.NotNullImplementor; import org.apache.calcite.adapter.enumerable.NullPolicy; import org.apache.calcite.adapter.enumerable.RexToLixTranslator; @@ -18,7 +15,9 @@ import org.apache.calcite.sql.type.ReturnTypes; import org.apache.calcite.sql.type.SqlReturnTypeInference; import org.apache.calcite.sql.type.SqlTypeTransforms; +import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.codec.digest.MessageDigestAlgorithms; import org.opensearch.sql.expression.function.ImplementorUDF; public class CryptographicFunction extends ImplementorUDF { @@ -36,28 +35,6 @@ public SqlReturnTypeInference getReturnTypeInference() { } public static class Sha2Implementor implements NotNullImplementor { - private static final ThreadLocal> digests; - - static { - digests = - ThreadLocal.withInitial( - () -> { - try { - return Map.of( - 224, - MessageDigest.getInstance("SHA-224"), - 256, - DigestUtils.getSha256Digest(), - 384, - DigestUtils.getSha384Digest(), - 512, - DigestUtils.getSha512Digest()); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - }); - } - @Override public Expression implement( RexToLixTranslator translator, RexCall call, List translatedOperands) { @@ -65,23 +42,17 @@ public Expression implement( } public static String getDigest(String input, int algorithm) { - if (!digests.get().containsKey(algorithm)) { - throw new IllegalArgumentException("Unsupported SHA2 algorithm: " + algorithm); - } - return getDigest(digests.get().get(algorithm), input); - } - - private static String getDigest(MessageDigest digest, String input) { - byte[] hash = digest.digest(input.getBytes()); - StringBuilder hexString = new StringBuilder(); - for (byte b : hash) { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); + return switch (algorithm) { + case 224 -> Hex.encodeHexString( + DigestUtils.getDigest(MessageDigestAlgorithms.SHA_224).digest(input.getBytes())); + case 256 -> DigestUtils.sha256Hex(input); + case 384 -> DigestUtils.sha384Hex(input); + case 512 -> DigestUtils.sha512Hex(input); + default -> throw new IllegalArgumentException( + String.format( + "Unsupported SHA2 algorithm: %d. Only 224, 256, 384, and 512 are supported.", + algorithm)); + }; } } }