From 4a21546575f8ecd5e607e40097923123b9a5785e Mon Sep 17 00:00:00 2001 From: Shivam Verma Date: Fri, 16 May 2025 21:39:22 +0530 Subject: [PATCH] HHH-19471 - Hashing a constraint name using SHA-256 if the MD5 algorithm is not available Co-authored-by: Yitzchak Weiser Co-authored-by: Ashwinkumar Rathod --- .../boot/model/naming/NamingHelper.java | 46 ++++++++++++----- .../binding/naming/NamingHelperTest.java | 49 +++++++++++++++++++ 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java index e1e515d89f6c..d110cf1e3837 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/naming/NamingHelper.java @@ -12,6 +12,7 @@ import java.util.List; import org.hibernate.HibernateException; +import org.jboss.logging.Logger; import static java.util.Comparator.comparing; @@ -30,6 +31,8 @@ public static NamingHelper withCharset(String charset) { private final String charset; + private static final Logger log = Logger.getLogger(NamingHelper.class); + public NamingHelper() { this(null); } @@ -124,8 +127,8 @@ public String generateHashedConstraintName( } /** - * Hash a constraint name using MD5. Convert the MD5 digest to base 35 - * (full alphanumeric), guaranteeing + * Hash a constraint name using MD5. If MD5 is not available, fall back to SHA-256. + * Convert the digest to base 35 (full alphanumeric), guaranteeing * that the length of the name will always be smaller than the 30 * character identifier restriction enforced by a few dialects. * @@ -135,17 +138,36 @@ public String generateHashedConstraintName( */ public String hashedName(String name) { try { - final MessageDigest md5 = MessageDigest.getInstance( "MD5" ); - md5.reset(); - md5.update( charset != null ? name.getBytes( charset ) : name.getBytes() ); - final BigInteger bigInt = new BigInteger( 1, md5.digest() ); - // By converting to base 35 (full alphanumeric), we guarantee - // that the length of the name will always be smaller than the 30 - // character identifier restriction enforced by a few dialects. - return bigInt.toString( 35 ); + return hashWithAlgorithm(name, "MD5"); } - catch ( NoSuchAlgorithmException | UnsupportedEncodingException e ) { - throw new HibernateException( "Unable to generate a hashed name", e ); + catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + log.infof("MD5 algorithm failed for hashedName, falling back to SHA-256: %s", e.getMessage()); + try { + return hashWithAlgorithm(name, "SHA-256"); + } + catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { + throw new HibernateException("Unable to generate a hashed name", ex); + } } } + + /** + * Helper to hash a name with the given algorithm and convert to base 35. + * + * @param name The name to be hashed. + * @param algorithm The hashing algorithm to use. + * + * @return String The hashed name. + */ + public String hashWithAlgorithm(String name, String algorithm) + throws NoSuchAlgorithmException, UnsupportedEncodingException { + final MessageDigest md = MessageDigest.getInstance(algorithm); + md.reset(); + md.update( charset != null ? name.getBytes( charset ) : name.getBytes() ); + final BigInteger bigInt = new BigInteger( 1, md.digest() ); + // By converting to base 35 (full alphanumeric), we guarantee + // that the length of the name will always be smaller than the 30 + // character identifier restriction enforced by a few dialects. + return bigInt.toString( 35 ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/naming/NamingHelperTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/naming/NamingHelperTest.java index ee90f10ce5fc..6ec13960eeaf 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/naming/NamingHelperTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/naming/NamingHelperTest.java @@ -9,13 +9,17 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; class NamingHelperTest { @@ -57,6 +61,51 @@ void smoke(String charset, String prefix, String tableName, String referencedTab .isEqualTo( expectedConstraintName ); } + @Test + void testHashWithAlgorithm_md5_utf8() throws Exception { + NamingHelper helper = NamingHelper.withCharset(StandardCharsets.UTF_8.name()); + String hash = helper.hashWithAlgorithm("table_name", "MD5"); + assertNotNull(hash); + assertFalse(hash.isEmpty()); + // MD5 hash of "table_name" in base 35 should be deterministic + assertEquals("8q6ok4ne4ufel54crtitkq7ir", hash); + } + + @Test + void testHashWithAlgorithm_sha256_utf8() throws Exception { + NamingHelper helper = NamingHelper.withCharset(StandardCharsets.UTF_8.name()); + String hash = helper.hashWithAlgorithm("table_name", "SHA-256"); + assertNotNull(hash); + assertFalse(hash.isEmpty()); + // SHA-256 hash of "table_name" in base 35 should be deterministic + assertEquals("nie2bx5e7mderevrnl4gkuhtmy45nwfvst7dv6cx3pb3yy9ul1", hash); + } + + @Test + void testHashWithAlgorithm_md5_iso88591() throws Exception { + NamingHelper helper = NamingHelper.withCharset(StandardCharsets.ISO_8859_1.name()); + String hash = helper.hashWithAlgorithm("café", "MD5"); + assertNotNull(hash); + assertFalse(hash.isEmpty()); + assertEquals("hgll69c0qdhsjikniholqfcj4", hash); + } + + @Test + void testHashWithAlgorithm_invalidAlgorithm() { + NamingHelper helper = NamingHelper.withCharset(StandardCharsets.UTF_8.name()); + assertThrows(NoSuchAlgorithmException.class, () -> { + helper.hashWithAlgorithm("table_name", "NOPE"); + }); + } + + @Test + void testHashWithAlgorithm_invalidCharset() { + NamingHelper helper = NamingHelper.withCharset("NOPE-CHARSET"); + assertThrows(UnsupportedEncodingException.class, () -> { + helper.hashWithAlgorithm("table_name", "MD5"); + }); + } + private static Stream args() { // String charset, String prefix, String tableName, String referencedTableName, // List columnNames, String expectedFkName, String expectedConstraintName