Skip to content

Commit 3323db7

Browse files
fixup! Generate stable sql objects names
Remove cryptographic hash
1 parent 9241c59 commit 3323db7

File tree

3 files changed

+121
-90
lines changed

3 files changed

+121
-90
lines changed

src/NHibernate.Test/MappingTest/TableFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void NameIsStable()
6666
var tbl = new Table { Name = "name" };
6767
Assert.That(
6868
Constraint.GenerateName("FK_", tbl, null, new[] {new Column("col1"), new Column("col2")}),
69-
Is.EqualTo("FK_IHNJ2ONUQL23X2DYGJ2YBBV3F7A"));
69+
Is.EqualTo("FK_3B355A0C"));
7070
}
7171
}
7272
}

src/NHibernate/Mapping/Constraint.cs

Lines changed: 3 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4-
using System.Security.Cryptography;
54
using System.Text;
65
using NHibernate.Engine;
76
using NHibernate.Util;
@@ -74,96 +73,13 @@ public static string GenerateName(
7473
// Hash the generated name for avoiding collisions with user choosen names.
7574
// This is not 100% reliable, as hashing may still have a chance of generating
7675
// collisions.
77-
var name = prefix + HashName(sb.ToString());
78-
79-
// Hibernate uses an algorithm yielding names shorter than 30 characters. But we cannot
80-
// use it (see HashName). And also we have DB limited to even less (Informix)...
81-
if (name.Length > 30)
82-
{
83-
// This, of course, increases the collision risk.
84-
name = name.Substring(0, 30);
85-
}
76+
// Hibernate uses MD5 here, which .Net standrad implementation is rejected by
77+
// FIPS enabled machine. Better use a non-cryptographic hash.
78+
var name = prefix + Hasher.HashToString(sb.ToString());
8679

8780
return name;
8881
}
8982

90-
#region Name generation support methods
91-
92-
/// <summary>
93-
/// Hash a constraint name. Convert the hash digest to base 32
94-
/// (full alphanumeric) for shortening the hash string representation
95-
/// while keeping it suitable for db names.
96-
/// </summary>
97-
/// <param name="name">The name to be hashed.</param>
98-
/// <returns>The hased name.</returns>
99-
private static string HashName(string name)
100-
{
101-
// Hibernate uses MD5, but with .Net this would throw on FIPS enabled machine.
102-
// As a consequence generated names will be quite longer.
103-
using (var hasher = SHA256.Create())
104-
{
105-
var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(name));
106-
// Converting to base 32 for shortening the name.
107-
// Hibernate uses base 35, but we do not have a native implementation
108-
// in .Net, and base 32 is easier to implement.
109-
return ToBase32String(hash);
110-
}
111-
}
112-
113-
// Adapted from https://stackoverflow.com/a/7135008/1178314
114-
// Changed for not padding with "="
115-
private static string ToBase32String(byte[] input)
116-
{
117-
if (input == null || input.Length == 0)
118-
{
119-
throw new ArgumentNullException(nameof(input));
120-
}
121-
122-
var charCount = (int) Math.Ceiling(input.Length / 5d) * 8;
123-
var result = new StringBuilder(charCount);
124-
125-
byte nextChar = 0, bitsRemaining = 5;
126-
127-
foreach (var b in input)
128-
{
129-
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
130-
result.Append(ValueToChar(nextChar));
131-
132-
if (bitsRemaining < 4)
133-
{
134-
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
135-
result.Append(ValueToChar(nextChar));
136-
bitsRemaining += 5;
137-
}
138-
139-
bitsRemaining -= 3;
140-
nextChar = (byte)((b << bitsRemaining) & 31);
141-
}
142-
143-
// If we didn't end with a full char
144-
if (result.Length != charCount)
145-
{
146-
result.Append(ValueToChar(nextChar));
147-
}
148-
149-
return result.ToString();
150-
}
151-
152-
private static char ValueToChar(byte b)
153-
{
154-
if (b < 26)
155-
{
156-
return (char)(b + 65);
157-
}
158-
159-
if (b < 32)
160-
{
161-
return (char)(b + 24);
162-
}
163-
164-
throw new ArgumentException("Byte is not a value Base32 value.", "b");
165-
}
166-
16783
private class ColumnComparator : IComparer<Column>
16884
{
16985
public static readonly ColumnComparator Instance = new ColumnComparator();
@@ -173,8 +89,6 @@ public int Compare(Column col1, Column col2) {
17389
}
17490
}
17591

176-
#endregion
177-
17892
/// <summary>
17993
/// Adds the <see cref="Column"/> to the <see cref="ICollection"/> of
18094
/// Columns that are part of the constraint.

src/NHibernate/Util/Hasher.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Derived from MurmurHash2Simple,
3+
* http://landman-code.blogspot.com/2009/02/c-superfasthash-and-murmurhash2.html
4+
*/
5+
6+
/***** BEGIN LICENSE BLOCK *****
7+
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
8+
*
9+
* The contents of this file are subject to the Mozilla Public License Version
10+
* 1.1 (the "License"); you may not use this file except in compliance with
11+
* the License. You may obtain a copy of the License at
12+
* http://www.mozilla.org/MPL/
13+
*
14+
* Software distributed under the License is distributed on an "AS IS" basis,
15+
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
16+
* for the specific language governing rights and limitations under the
17+
* License.
18+
*
19+
* The Original Code is HashTableHashing.MurmurHash2.
20+
*
21+
* The Initial Developer of the Original Code is
22+
* Davy Landman.
23+
* Portions created by the Initial Developer are Copyright (C) 2009
24+
* the Initial Developer. All Rights Reserved.
25+
*
26+
* Contributor(s):
27+
*
28+
*
29+
* Alternatively, the contents of this file may be used under the terms of
30+
* either the GNU General Public License Version 2 or later (the "GPL"), or
31+
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32+
* in which case the provisions of the GPL or the LGPL are applicable instead
33+
* of those above. If you wish to allow use of your version of this file only
34+
* under the terms of either the GPL or the LGPL, and not to allow others to
35+
* use your version of this file under the terms of the MPL, indicate your
36+
* decision by deleting the provisions above and replace them with the notice
37+
* and other provisions required by the GPL or the LGPL. If you do not delete
38+
* the provisions above, a recipient may use your version of this file under
39+
* the terms of any one of the MPL, the GPL or the LGPL.
40+
*
41+
* ***** END LICENSE BLOCK ***** */
42+
43+
using System;
44+
using System.Text;
45+
46+
namespace NHibernate.Util
47+
{
48+
/// <summary>A stable hasher using MurmurHash2 algorithm.</summary>
49+
internal static class Hasher
50+
{
51+
internal static string HashToString(string input)
52+
{
53+
var hash = Hash(input);
54+
return hash.ToString("X");
55+
}
56+
57+
internal static uint Hash(string input)
58+
{
59+
return Hash(Encoding.UTF8.GetBytes(input));
60+
}
61+
62+
internal static uint Hash(byte[] data)
63+
{
64+
return Hash(data, 0xc58f1a7b);
65+
}
66+
67+
private const uint _m = 0x5bd1e995;
68+
private const int _r = 24;
69+
70+
internal static uint Hash(byte[] data, uint seed)
71+
{
72+
var length = data.Length;
73+
if (length == 0)
74+
return 0;
75+
var h = seed ^ (uint) length;
76+
var currentIndex = 0;
77+
while (length >= 4)
78+
{
79+
var k = BitConverter.ToUInt32(data, currentIndex);
80+
k *= _m;
81+
k ^= k >> _r;
82+
k *= _m;
83+
84+
h *= _m;
85+
h ^= k;
86+
currentIndex += 4;
87+
length -= 4;
88+
}
89+
90+
switch (length)
91+
{
92+
case 3:
93+
h ^= BitConverter.ToUInt16(data, currentIndex);
94+
h ^= (uint) data[currentIndex + 2] << 16;
95+
h *= _m;
96+
break;
97+
case 2:
98+
h ^= BitConverter.ToUInt16(data, currentIndex);
99+
h *= _m;
100+
break;
101+
case 1:
102+
h ^= data[currentIndex];
103+
h *= _m;
104+
break;
105+
}
106+
107+
// Do a few final mixes of the hash to ensure the last few
108+
// bytes are well-incorporated.
109+
110+
h ^= h >> 13;
111+
h *= _m;
112+
h ^= h >> 15;
113+
114+
return h;
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)