这是this question的后续内容。
接受的答案通常已足够,但要求用户提供用于生成密钥的个人信息(例如姓名)。
我想知道是否可以基于公共种子生成不同的密钥,以便程序能够验证这些密钥是否属于特定产品,但不会使这个过程对最终用户显而易见。
我的意思是它可能是产品ID的哈希加上一些随机的字符序列,但这将允许用户猜测潜在的新密钥。应该有某种算法难以猜测。
答案 0 :(得分:6)
标准对称/不对称/散列算法(MDx,SHAx,RSA等)的问题是它们不能在非常短的字符串上操作,例如20/30字符长的产品密钥字符串。如果你能给你的用户〜1000个字符长的字符串,那么我会选择这些算法。
如果你不能,这里有一些C#代码能够建立一个“几乎完全随机”的短产品密钥,但其中有一个隐藏的字节。你可以把你想要的任何东西放在那个“隐藏字节”中,并且密钥遵循一些不容易猜到的算法。您可以验证传入的字符串&使用TryReadKey()重新读取字节:
string key = BuildKey(myHiddenByte); // give that to the end user
...
byte hidden;
if (!TryReadKey(key, out hidden))
{
Console.WriteLine("key is not ok");
}
else
{
Console.WriteLine("key is ok and hidden byte is " + hidden);
}
该算法使用Steganography原则,当然不是防弹,可以改进,但可能有用。注意密钥生成可能需要一些时间,这是正常的。它产生这样的键(注意第一个字是6个字符长,其他5个字符长):
KZGMB0-XYJC2-YRKH3-8LD8G-5FUZG
YKU93K-M34PD-E5PL0-QM91J-QLDZF
DH27H9-NCW8E-EMGPL-YCJXJ-N2PRG
WDAKDE-G56NR-6BA3R-0JE6U-625EB
6D5EJ0-NJDAK-EMGZR-Z6ZDF-JHJGF
....
以下是代码:
// need 32 characters, so we can use a 5-bit base encoding
// 0 1 2 3
// 01234567890123456789012345678901
private const string KeyChars = "ABCDEFGHJKLMNPQRTUWXYZ0123456789";
private const int MinSum = 2000;
private const int Mask = 0xFFFFF; // beware, this can have a dramatic influence on performance
/// <summary>
/// Builds a key and hides data in it.
/// Key format is XXXXXX-XXXXX-XXXXX-XXXXX-XXXXX.
/// </summary>
/// <param name="hiddenData">The hidden data.</param>
/// <returns>The build key</returns>
public static string BuildKey(byte hiddenData)
{
int index;
Guid guid;
uint[] word = new uint[4];
byte[] array;
while (true)
{
// we use guid initial randomness characteristics
guid = Guid.NewGuid();
array = guid.ToByteArray();
int sum = array.Aggregate(0, (current, b) => current + b);
// a simple check to make sure the guid is not filled with too much zeros...
if (sum < MinSum)
continue;
// build a 32-bit word array
for (int i = 0; i < 4; i++)
{
word[i] = BitConverter.ToUInt32(array, i * 4) & Mask;
}
// Here we check the guid follows some algorithm. if it doesn't we skip it
// the algorithm presented here is to check one of the 32-bit word is equal to the sum of the others
// (modulo a mask otherwise it takes too long!)
//
// This algorithm can be changed at your will (and change the TryReadKey as well)
if ((word[0] + word[1] + word[2]) == word[3])
{
index = 3;
break;
}
if ((word[0] + word[1] + word[3]) == word[2])
{
index = 2;
break;
}
if ((word[0] + word[3] + word[2]) == word[1])
{
index = 1;
break;
}
if ((word[3] + word[1] + word[2]) == word[0])
{
index = 0;
break;
}
}
// hidden info is also xor'd with other words hi order (except the one we masked)
// so it's not too easy to guess
hiddenData = (byte)(hiddenData ^ array[0] ^ array[4] ^ array[8] ^ array[12]);
// rebuild words without mask
for (int i = 0; i < 4; i++)
{
word[i] = BitConverter.ToUInt32(array, i * 4);
}
// hidden info is stored in the block that is the sum of other blocks, in hi order byte (outside the mask)
word[index] &= 0x00FFFFFF;
word[index] |= ((uint)hiddenData) << 24;
// rebuild the array back
for (int i = 0; i < 4; i++)
{
byte[] ui = BitConverter.GetBytes(word[i]);
for (int j = 0; j < 4; j++)
{
array[i * 4 + j] = ui[j];
}
}
// now use 5-bits encoding
int encodingBitIndex = 0;
StringBuilder key = new StringBuilder();
while (encodingBitIndex < 128)
{
byte encodingByte = Get5Bits(array, encodingBitIndex);
if ((key.Length > 0) && (key.Length % 6) == 0)
{
key.Append('-');
}
key.Append(KeyChars[encodingByte]);
encodingBitIndex += 5;
}
return key.ToString();
}
/// <summary>
/// Validates the specified key and reads hidden data from it.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="hiddenData">The hidden data.</param>
/// <returns>true if the key is valid and hidden data was read; false otherwise.</returns>
public static bool TryReadKey(string key, out byte hiddenData)
{
hiddenData = 0;
if (key == null)
return false;
key = key.Replace("-", string.Empty);
if (key.Length != 26)
return false;
byte[] array = new byte[16];
int encodingBitIndex = 0;
foreach (char t in key)
{
byte b = 255;
for (byte k = 0; k < KeyChars.Length; k++)
{
if (KeyChars[k] != t) continue;
b = k;
break;
}
if (b == 255) // char not found
return false;
Put5Bits(array, encodingBitIndex, b);
encodingBitIndex += 5;
}
// quick sum check
int sum = array.Aggregate(0, (current, b) => current + b);
// add 256 because we changed the hidden byte
sum += 256;
if (sum < MinSum)
return false;
uint[] word = new uint[4];
for (int i = 0; i < 4; i++)
{
word[i] = BitConverter.ToUInt32(array, i * 4) & Mask;
}
// This must match BuildKey algorithm
int index;
if ((word[0] + word[1] + word[2]) == word[3])
{
index = 3;
}
else if ((word[0] + word[1] + word[3]) == word[2])
{
index = 2;
}
else if ((word[0] + word[3] + word[2]) == word[1])
{
index = 1;
}
else if ((word[3] + word[1] + word[2]) == word[0])
{
index = 0;
}
else
return false;
// reread word & extract hidden byte back
word[index] = BitConverter.ToUInt32(array, index * 4);
hiddenData = (byte)(word[index] >> 24);
hiddenData = (byte)(hiddenData ^ array[0] ^ array[4] ^ array[8] ^ array[12]);
return true;
}
// get 5 bits from a byte buffer at an arbitrary bit index
private static byte Get5Bits(byte[] buffer, int bitIndex)
{
int r = bitIndex % 8;
if (r < 4)
return (byte)(((buffer[bitIndex / 8]) & (0xFF >> r)) >> (3 - r));
byte b0 = (byte)((buffer[bitIndex / 8] & (0xFF >> r)) << (r - 3));
if ((1 + (bitIndex / 8)) == buffer.Length) // last
return (byte)(buffer[buffer.Length - 1] & 0x7);
if ((bitIndex / 8) < 16)
return (byte)(b0 | buffer[1 + (bitIndex / 8)] >> (11 - r));
return b0;
}
// put 5 bits into a byte buffer at an arbitrary bit index
private static void Put5Bits(byte[] buffer, int bitIndex, byte value)
{
int r = bitIndex % 8;
if (r < 4)
{
buffer[bitIndex / 8] |= (byte)((value << (3 - r)));
}
else
{
if ((1 + (bitIndex / 8)) == buffer.Length) // last
{
buffer[buffer.Length - 1] |= (byte)(value & 0x7);
}
else if ((bitIndex / 8) < 16)
{
buffer[bitIndex / 8] |= (byte)((value >> (r - 3)));
buffer[1 + (bitIndex / 8)] |= (byte)((value << (11 - r)));
}
}
}
答案 1 :(得分:0)
您可以使用由密钥生成的数字签名以及插入程序的公钥。