最近工作中要用到这个算法,从而实现数据交换时的安全。
在flutter/dart的库中没有找到直接能使用的,所以去找了几个版本,最后用C#的一个版本做了移植(原代码出处请看代码内注释)。
希望对需要的人有帮助。
代码如下:
//
// DiffieHellmanManaged.cs: Implements the Diffie-Hellman key agreement algorithm
//
// Author:
// Pieter Philippaerts (Pieter@mentalis.org)
//
// (C) 2003 The Mentalis.org Team (http://www.mentalis.org/)
//
// References:
// - PKCS#3 [http://www.rsasecurity.com/rsalabs/pkcs/pkcs-3/]
//
import 'dart:math';
import 'dart:typed_data';
import 'util.dart';
/// <summary>
/// Implements the Diffie-Hellman algorithm.
/// </summary>
class DiffieHellmanManaged {
/// <summary>
/// Initializes a new <see cref="DiffieHellmanManaged"/> instance.
/// </summary>
/// <param name="bitlen">The length, in bits, of the public P parameter.</param>
/// <param name="l">The length, in bits, of the secret value X. This parameter can be set to 0 to use the default size.</param>
/// <param name="keygen">One of the <see cref="DHKeyGeneration"/> values.</param>
/// <remarks>The larger the bit length, the more secure the algorithm is. The default is 1024 bits. The minimum bit length is 128 bits.<br/>The size of the private value will be one fourth of the bit length specified.</remarks>
/// <exception cref="ArgumentException">The specified bit length is invalid.</exception>
DiffieHellmanManaged(int bitlen, int l, DHKeyGeneration keygen) {
if (bitlen < 256 || l < 0)
throw Exception('invliad bitlength');
//BigInt p, g;
var ary = GenerateKey(bitlen, keygen);
_initialize(ary[0], ary[1], null, l, false);
}
/// <summary>
/// Initializes a new <see cref="DiffieHellmanManaged"/> instance.
/// </summary>
/// <param name="p">The P parameter of the Diffie-Hellman algorithm. This is a public parameter.</param>
/// <param name="g">The G parameter of the Diffie-Hellman algorithm. This is a public parameter.</param>
/// <param name="x">The X parameter of the Diffie-Hellman algorithm. This is a private parameter. If this parameters is a null reference (<b>Nothing</b> in Visual Basic), a secret value of the default size will be generated.</param>
DiffieHellmanManaged.init(Uint8List p, Uint8List g, [Uint8List x]) {
if (p == null || g == null)
throw new Exception('invalid parameters');
if (x == null)
_initialize(Util.decodeBigInt(p), Util.decodeBigInt(g), null, 0, true);
else
_initialize(Util.decodeBigInt(p), Util.decodeBigInt(g), Util.decodeBigInt(x), 0, true);
}
DiffieHellmanManaged.init2(BigInt p, BigInt g,[int secretLen = 0,BigInt x]) {
if (p == null || g == null)
throw new Exception('invalid parameters');
_initialize(p, g, x, secretLen, true);
}
//is prime: https://www.codeproject.com/articles/2728/c-biginteger-class
// initializes the private variables (throws CryptographicException)
void _initialize(BigInt p, BigInt g, BigInt x, int secretLen, bool checkInput) {
//if (!p.isProbablePrime() || g <= 0 || g >= p || (x != null && (x <= 0 || x > p - 2)))
// throw new CryptographicException();
if ( g <= BigInt.zero || g >= p || (x != null && (x <= BigInt.zero || x > p - BigInt.from(2))))
throw Exception('Invalid Paramters');
m_P = p;
m_G = g;
// default is to generate a number as large as the prime this
// is usually overkill, but it's the most secure thing we can
// do if the user doesn't specify a desired secret length ...
if (secretLen == 0)
secretLen = m_P.bitLength; //p.bitCount();
if (x == null) {
m_X = BigInt.zero;
BigInt pm1 = m_P - BigInt.from(1);
//for (m_X = BigInt.genRandom(secretLen); m_X >= pm1 || m_X == 0; m_X = BigInt.genRandom(secretLen)) {}
while (m_X >= pm1 || m_X == BigInt.zero) {
var re = _randBytes(Random(DateTime
.now()
.millisecond), (secretLen / 8).toInt());
m_X = Util.decodeBigInt(re);
}
} else {
m_X = x;
}
}
/// Returns [n] random bytes.
Uint8List _randBytes(Random generator, int n) {
final Uint8List random = Uint8List(n);
for (int i = 0; i < random.length; i++) {
random[i] = generator.nextInt(255);
}
return random;
}
/// <summary>
/// Creates the key exchange data.
/// </summary>
/// <returns>The key exchange data to be sent to the intended recipient.</returns>
Uint8List CreateKeyExchange() {
BigInt y = m_G.modPow(m_X, m_P);
//byte[] ret = y.getBytes();
var re = Util.encodeBigInt(y);
y = BigInt.zero;
return re;
}
/// <summary>
/// Extracts secret information from the key exchange data.
/// </summary>
/// <param name="keyEx">The key exchange data within which the shared key is hidden.</param>
/// <returns>The shared key derived from the key exchange data.</returns>
Uint8List DecryptKeyExchange(Uint8List keyEx) {
//BigInt pvr = new BigInt(keyEx);
var pvr = Util.decodeBigInt(keyEx);
BigInt z = pvr.modPow(m_X, m_P);
//byte[] ret = z.getBytes();
var re = Util.encodeBigInt(z);
z = BigInt.zero;
return re;
}
/// <summary>
/// Releases the unmanaged resources used by the SymmetricAlgorithm and optionally releases the managed resources.
/// </summary>
/// <param name="disposing"><b>true</b> to release both managed and unmanaged resources; <b>false</b> to release only unmanaged resources.</param>
// void Dispose(bool disposing) {
// if (!m_Disposed) {
// m_P = BigInt.zero;
// m_G = BigInt.zero;
// m_X = BigInt.zero;
// }
// m_Disposed = true;
// }
/// <summary>
/// Exports the <see cref="DHParameters"/>.
/// </summary>
/// <param name="includePrivateParameters"><b>true</b> to include private parameters; otherwise, <b>false</b>.</param>
/// <returns>The parameters for <see cref="DiffieHellman"/>.</returns>
DHParameters ExportParameters(bool includePrivateParameters) {
DHParameters ret = new DHParameters();
ret.P = Util.encodeBigInt(m_P);//.getBytes();
ret.G = Util.encodeBigInt(m_G);//.getBytes();
if (includePrivateParameters) {
ret.X = Util.encodeBigInt(m_X);//.getBytes();
}
return ret;
}
// /// <summary>
// /// Imports the specified <see cref="DHParameters"/>.
// /// </summary>
// /// <param name="parameters">The parameters for <see cref="DiffieHellman"/>.</param>
// /// <exception cref="CryptographicException"><paramref name="P"/> or <paramref name="G"/> is a null reference (<b>Nothing</b> in Visual Basic) -or- <paramref name="P"/> is not a prime number.</exception>
// void ImportParameters(DHParameters parameters) {
// if (parameters.P == null)
// throw new CryptographicException("Missing P value.");
// if (parameters.G == null)
// throw new CryptographicException("Missing G value.");
//
// BigInt p = new BigInt(parameters.P),
// g = new BigInt(parameters.G),
// x = null;
// if (parameters.X != null) {
// x = new BigInt(parameters.X);
// }
// Initialize(p, g, x, 0, true);
// }
//TODO: implement DH key generation methods
//return P,G
List<BigInt> GenerateKey(int bitlen, DHKeyGeneration keygen) {
BigInt p, g;
if (keygen == DHKeyGeneration.Static) {
if (bitlen == 768)
p = Util.decodeBigInt(m_OAKLEY768);
else if (bitlen == 1024)
p = Util.decodeBigInt(m_OAKLEY1024);
else if (bitlen == 1536)
p = Util.decodeBigInt(m_OAKLEY1536);
else
throw Exception("Invalid bit size.");
g = BigInt.from(22); // all OAKLEY keys use 22 as generator
//} else if (keygen == DHKeyGeneration.SophieGermain) {
// throw new NotSupportedException(); //TODO
//} else if (keygen == DHKeyGeneration.DSA) {
// 1. Let j = (p - 1)/q.
// 2. Set h = any integer, where 1 < h < p - 1
// 3. Set g = h^j mod p
// 4. If g = 1 go to step 2
// BigInt j = (p - 1) / q;
}
else { // random
//p = BigInt.genPseudoPrime(bitlen);
p = Util.decodeBigInt(_randBytes(Random(DateTime
.now()
.millisecond), (bitlen / 8).toInt()));
g = new BigInt.from(3); // always use 3 as a generator
}
return [p,g];
}
BigInt m_P;
BigInt m_G;
BigInt m_X;
//bool m_Disposed;
final m_OAKLEY768 = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
0xA6, 0x3A, 0x36, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
];
final m_OAKLEY1024 = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11,
0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
;
final m_OAKLEY1536 = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11,
0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D,
0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36,
0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56,
0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D,
0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08,
0xCA, 0x23, 0x73, 0x27, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
];
}
enum DHKeyGeneration {
/// <summary>
/// [TODO] you first randomly select a prime Q of size 160 bits, then choose P randomly among numbers like
/// Q*R+1 with R random. Then you go along with finding a generator G which has order exactly Q. The private
/// key X is then a number modulo Q.
/// [FIPS 186-2-Change1 -- http://csrc.nist.gov/publications/fips/]
/// </summary>
// see RFC2631 [http://www.faqs.org/rfcs/rfc2631.html]
//DSA,
/// <summary>
/// Returns dynamically generated values for P and G. Unlike the Sophie Germain or DSA key generation methods,
/// this method does not ensure that the selected prime offers an adequate security level.
/// </summary>
Random,
/// <summary>
/// Returns dynamically generated values for P and G. P is a Sophie Germain prime, which has some interesting
/// security features when used with Diffie Hellman.
/// </summary>
//SophieGermain,
/// <summary>
/// Returns values for P and G that are hard coded in this library. Contrary to what your intuition may tell you,
/// using these hard coded values is perfectly safe.
/// The values of the P and G parameters are taken from 'The OAKLEY Key Determination Protocol' [RFC2412].
/// This is the prefered key generation method, because it is very fast and very safe.
/// Because this method uses fixed values for the P and G parameters, not all bit sizes are supported.
/// The current implementation supports bit sizes of 768, 1024 and 1536.
/// </summary>
Static
}
/// <summary>
/// Represents the parameters of the Diffie-Hellman algorithm.
/// </summary>
class DHParameters {
/// <summary>
/// Represents the public <b>P</b> parameter of the Diffie-Hellman algorithm.
/// </summary>
Uint8List P;
/// <summary>
/// Represents the public <b>G</b> parameter of the Diffie-Hellman algorithm.
/// </summary>
Uint8List G;
/// <summary>
/// Represents the private <b>X</b> parameter of the Diffie-Hellman algorithm.
/// </summary>
Uint8List X;
}
注:里面的 isProbablePrime() 判断未实现。
测试代码:
var dh1 = DiffieHellmanManaged(1024,1024,DHKeyGeneration.Static);
var key1 = dh1.CreateKeyExchange();
print("key1:$key1");
var dh2 = DiffieHellmanManaged.init2(dh1.m_P, dh1.m_G);
var key2 = dh2.CreateKeyExchange();
print("key2:$key2");
var secretKey1 = dh1.DecryptKeyExchange(key2);
print("exchanged secret from key1:$secretKey1");
var secretKye2 = dh2.DecryptKeyExchange(key1);
print("exchanged secret from key2:$secretKye2");
expect(secretKey1,secretKey1);
--END--