Skip to content

HHH-19471 - Replace hashing a constraint name using SHA-256 instead of MD5 algorithm #10169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.List;

import org.hibernate.HibernateException;
import org.jboss.logging.Logger;

import static java.util.Comparator.comparing;

Expand All @@ -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);
}
Expand Down Expand Up @@ -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.
*
Expand All @@ -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 );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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<Arguments> args() {
// String charset, String prefix, String tableName, String referencedTableName,
// List<String> columnNames, String expectedFkName, String expectedConstraintName
Expand Down