Skip to content

Commit 521a2ef

Browse files
authored
Improve DH safe-prime generation and RSA modulus validation (#103)
Move small-prime screening and CreateRandomPrime into BigIntegerUtilities, add external RSA modulus checks (size, small factors, enhanced MR) with AIsInternal for generated keys, and expose MaxSize/MaxMRTests as static class properties. Rewrite DH safe-prime generation for g=2 with Pocklington/Rabin–Miller screening.
1 parent 46c19ca commit 521a2ef

6 files changed

Lines changed: 321 additions & 152 deletions

File tree

CryptoLib.Tests/src/Crypto/RSATests.pas

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ TTestRSA = class(TCryptoLibAlgorithmTestCase)
8989
procedure TestRsaSignature;
9090
procedure TestRsaPublicKeyInfoEncodingHasNullParameters;
9191
procedure TestSubjectPublicKeyInfoFactoryRsaConsistency;
92+
procedure TestMaxSizeRejectsOversizedModulus;
93+
procedure TestMaxMRTestsZeroSkipsCompositeCheck;
94+
procedure TestMaxSizeMaxMRTestsUnsetDefault;
9295

9396
end;
9497

@@ -443,6 +446,66 @@ procedure TTestRSA.TestSubjectPublicKeyInfoFactoryRsaConsistency;
443446
'RSA AlgorithmIdentifier parameters should be DerNull.');
444447
end;
445448

449+
procedure TTestRSA.TestMaxSizeRejectsOversizedModulus;
450+
var
451+
LOldMaxSize, LOldMaxMRTests: Int32;
452+
begin
453+
LOldMaxSize := TRsaKeyParameters.MaxSize;
454+
LOldMaxMRTests := TRsaKeyParameters.MaxMRTests;
455+
try
456+
TRsaKeyParameters.MaxSize := 512;
457+
CheckTrue(FModulus.BitLength > 512, 'test modulus must exceed MaxSize cap');
458+
try
459+
TRsaKeyParameters.Create(False, FModulus, FPubExp);
460+
Fail('expected EArgumentCryptoLibException for oversized modulus');
461+
except
462+
on E: EArgumentCryptoLibException do
463+
CheckEquals('RSA modulus out of range', E.Message);
464+
end;
465+
finally
466+
TRsaKeyParameters.MaxSize := LOldMaxSize;
467+
TRsaKeyParameters.MaxMRTests := LOldMaxMRTests;
468+
end;
469+
end;
470+
471+
procedure TTestRSA.TestMaxMRTestsZeroSkipsCompositeCheck;
472+
var
473+
LOldMaxSize, LOldMaxMRTests: Int32;
474+
LParams: TRsaKeyParameters;
475+
begin
476+
LOldMaxSize := TRsaKeyParameters.MaxSize;
477+
LOldMaxMRTests := TRsaKeyParameters.MaxMRTests;
478+
try
479+
TRsaKeyParameters.MaxMRTests := 0;
480+
LParams := TRsaKeyParameters.Create(False, FModulus, FPubExp);
481+
CheckTrue(LParams.Modulus.Equals(FModulus), 'modulus should be accepted when MR is disabled');
482+
finally
483+
TRsaKeyParameters.MaxSize := LOldMaxSize;
484+
TRsaKeyParameters.MaxMRTests := LOldMaxMRTests;
485+
end;
486+
end;
487+
488+
procedure TTestRSA.TestMaxSizeMaxMRTestsUnsetDefault;
489+
var
490+
LOldMaxSize, LOldMaxMRTests: Int32;
491+
LParams: TRsaKeyParameters;
492+
begin
493+
LOldMaxSize := TRsaKeyParameters.MaxSize;
494+
LOldMaxMRTests := TRsaKeyParameters.MaxMRTests;
495+
try
496+
TRsaKeyParameters.MaxSize := -1;
497+
TRsaKeyParameters.MaxMRTests := -1;
498+
CheckEquals(-1, TRsaKeyParameters.MaxSize, 'unset MaxSize should be -1');
499+
CheckEquals(-1, TRsaKeyParameters.MaxMRTests, 'unset MaxMRTests should be -1');
500+
LParams := TRsaKeyParameters.Create(False, FModulus, FPubExp);
501+
CheckTrue(LParams.Modulus.Equals(FModulus),
502+
'default limits should accept standard test modulus');
503+
finally
504+
TRsaKeyParameters.MaxSize := LOldMaxSize;
505+
TRsaKeyParameters.MaxMRTests := LOldMaxMRTests;
506+
end;
507+
end;
508+
446509
initialization
447510

448511
{$IFDEF FPC}

CryptoLib/src/Crypto/Generators/ClpDHGenerators.pas

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,13 @@ function TDHParametersGenerator.GenerateParameters: IDHParameters;
115115
LSafePrimes: TCryptoLibGenericArray<TBigInteger>;
116116
LP, LQ, LG: TBigInteger;
117117
begin
118-
LSafePrimes := TDHParametersHelper.GenerateSafePrimes(FSize, FCertainty, FRandom);
118+
LSafePrimes := TDHParametersHelper.GenerateSafePrimes(FSize, FCertainty, FRandom, True);
119119
LP := LSafePrimes[0];
120120
LQ := LSafePrimes[1];
121-
LG := TDHParametersHelper.SelectGenerator(LP, LQ, FRandom);
121+
{$IFDEF DEBUG}
122+
Assert((LP.Int32ValueExact and 7) = 7);
123+
{$ENDIF DEBUG}
124+
LG := TBigInteger.Two;
122125
Result := TDHParameters.Create(LP, LG, LQ, TBigInteger.Two, nil);
123126
end;
124127

CryptoLib/src/Crypto/Generators/ClpDHParametersHelper.pas

Lines changed: 93 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,22 @@ interface
2323
uses
2424
ClpISecureRandom,
2525
ClpBigInteger,
26-
ClpBigIntegerUtilities,
2726
ClpWNafUtilities,
2827
ClpBitOperations,
2928
ClpCryptoLibTypes;
3029

30+
resourcestring
31+
SSizeTooSmall = 'size < 64';
32+
3133
type
3234
TDHParametersHelper = class sealed(TObject)
3335

3436
strict private
3537
class var
3638

37-
FSix: TBigInteger;
39+
FTwo: TBigInteger;
40+
FTwelve: TBigInteger;
41+
FTwentyFour: TBigInteger;
3842
FPrimeProducts: TCryptoLibInt32Array;
3943
FPrimeLists: TCryptoLibMatrixInt32Array;
4044
FBigPrimeProducts: TCryptoLibGenericArray<TBigInteger>;
@@ -43,34 +47,24 @@ TDHParametersHelper = class sealed(TObject)
4347
class function ConstructBigPrimeProducts(const APrimeProducts
4448
: TCryptoLibInt32Array): TCryptoLibGenericArray<TBigInteger>; static;
4549

50+
class function HasAnySmallFactorsSafe(const X: TBigInteger): Boolean; static;
51+
4652
class procedure Boot(); static;
4753

4854
class constructor DHParametersHelper();
4955

5056
public
5157

5258
/// <summary>
53-
/// <para>
54-
/// Finds a pair of prime BigInteger's {p, q: p = 2q + 1}
55-
/// </para>
56-
/// <para>
57-
/// (see: Handbook of Applied Cryptography 4.86)
58-
/// </para>
59-
/// </summary>
60-
class function GenerateSafePrimes(ASize, ACertainty: Int32;
61-
const ARandom: ISecureRandom): TCryptoLibGenericArray<TBigInteger>; static;
62-
63-
/// <summary>
64-
/// <para>
65-
/// Select a high order element of the multiplicative group Zp*
66-
/// </para>
67-
/// <para>
68-
/// p and q must be s.t. p = 2*q + 1, where p and q are prime (see
69-
/// generateSafePrimes)
70-
/// </para>
59+
/// Finds a pair of prime BigInteger's {p, q: p = 2q + 1}.
7160
/// </summary>
72-
class function SelectGenerator(const AP, AQ: TBigInteger;
73-
const ARandom: ISecureRandom): TBigInteger; static;
61+
/// <remarks>
62+
/// See: Handbook of Applied Cryptography 4.86. If AForGenerator2 is true, the
63+
/// returned p will also have 2 as a quadratic residue (p === 7 mod 8).
64+
/// </remarks>
65+
class function GenerateSafePrimes(ABitLength, ACertainty: Int32;
66+
const ARandom: ISecureRandom; AForGenerator2: Boolean)
67+
: TCryptoLibGenericArray<TBigInteger>; static;
7468
end;
7569

7670
implementation
@@ -81,7 +75,9 @@ class procedure TDHParametersHelper.Boot;
8175
begin
8276
if not FIsBooted then
8377
begin
84-
FSix := TBigInteger.ValueOf(6);
78+
FTwo := TBigInteger.Two;
79+
FTwelve := TBigInteger.ValueOf(12);
80+
FTwentyFour := TBigInteger.ValueOf(24);
8581

8682
FPrimeLists := TBigInteger.primeLists;
8783
FPrimeProducts := TBigInteger.primeProducts;
@@ -112,105 +108,103 @@ class function TDHParametersHelper.ConstructBigPrimeProducts(const APrimeProduct
112108
Result := LBpp;
113109
end;
114110

115-
class function TDHParametersHelper.GenerateSafePrimes(ASize, ACertainty: Int32;
116-
const ARandom: ISecureRandom): TCryptoLibGenericArray<TBigInteger>;
111+
class function TDHParametersHelper.HasAnySmallFactorsSafe(const X: TBigInteger): Boolean;
117112
var
118-
LP, LQ: TBigInteger;
119-
LQLength, LMinWeight, LI, LTest, LRem3, LDiff, LJ, LPrime, LQRem: Int32;
120-
LRetryFlag: Boolean;
113+
LI, LJ, LR, LPrime: Int32;
121114
LPrimeList: TCryptoLibInt32Array;
122115
begin
123-
LRetryFlag := False;
124-
LQLength := ASize - 1;
125-
LMinWeight := TBitOperations.Asr32(ASize, 2);
126-
127-
if ASize <= 32 then
116+
for LI := 0 to System.Pred(System.Length(FPrimeLists)) do
128117
begin
129-
while True do
118+
LR := X.Remainder(FBigPrimeProducts[LI]).Int32ValueExact;
119+
120+
LPrimeList := FPrimeLists[LI];
121+
for LJ := 0 to System.Pred(System.Length(LPrimeList)) do
130122
begin
131-
LQ := TBigInteger.Create(LQLength, 2, ARandom);
123+
LPrime := LPrimeList[LJ];
124+
if (LR mod LPrime) < 2 then
125+
Exit(True);
126+
end;
127+
end;
132128

133-
LP := LQ.ShiftLeft(1).Add(TBigInteger.One);
129+
Result := False;
130+
end;
134131

135-
if not LP.IsProbablePrime(ACertainty, True) then
136-
Continue;
132+
class function TDHParametersHelper.GenerateSafePrimes(ABitLength, ACertainty: Int32;
133+
const ARandom: ISecureRandom; AForGenerator2: Boolean)
134+
: TCryptoLibGenericArray<TBigInteger>;
135+
var
136+
LP, LQ, LStep: TBigInteger;
137+
LLowBitsSet, LInc3, LMinWeight, LByteLength, LExtraBits, LCount, LPMod3: Int32;
138+
LBytes: TCryptoLibByteArray;
139+
begin
140+
if ABitLength < 64 then
141+
raise EArgumentCryptoLibException.CreateRes(@SSizeTooSmall);
137142

138-
if (ACertainty > 2) and (not LQ.IsProbablePrime(ACertainty, True)) then
139-
Continue;
143+
LLowBitsSet := $03;
144+
LInc3 := 4;
145+
LStep := FTwelve;
140146

141-
Break;
142-
end;
143-
end
144-
else
147+
if AForGenerator2 then
145148
begin
146-
while True do
147-
begin
148-
LQ := TBigInteger.Create(LQLength, 0, ARandom);
149+
LLowBitsSet := $07;
150+
LInc3 := -8;
151+
LStep := FTwentyFour;
152+
end;
149153

150-
LI := 0;
151-
while LI < System.Length(FPrimeLists) do
152-
begin
153-
LTest := LQ.Remainder(FBigPrimeProducts[LI]).Int32Value;
154+
LMinWeight := TBitOperations.Asr32(ABitLength, 2);
155+
LByteLength := (ABitLength + 7) div 8;
156+
LExtraBits := LByteLength * 8 - ABitLength;
154157

155-
if LI = 0 then
156-
begin
157-
LRem3 := LTest mod 3;
158-
if LRem3 <> 2 then
159-
begin
160-
LDiff := (2 * LRem3) + 2;
161-
LQ := LQ.Add(TBigInteger.ValueOf(LDiff));
162-
LTest := (LTest + LDiff) mod FPrimeProducts[LI];
163-
end;
164-
end;
158+
System.SetLength(LBytes, LByteLength);
165159

166-
LPrimeList := FPrimeLists[LI];
167-
for LJ := 0 to System.Pred(System.Length(LPrimeList)) do
168-
begin
169-
LPrime := LPrimeList[LJ];
170-
LQRem := LTest mod LPrime;
171-
if (LQRem = 0) or (LQRem = TBitOperations.Asr32(LPrime, 1)) then
172-
begin
173-
LQ := LQ.Add(FSix);
174-
LRetryFlag := True;
175-
Break;
176-
end;
177-
end;
178-
179-
if LRetryFlag then
180-
begin
181-
LI := 0;
182-
LRetryFlag := False;
183-
end
184-
else
185-
System.Inc(LI);
186-
end;
160+
while True do
161+
begin
162+
ARandom.NextBytes(LBytes);
187163

188-
if LQ.BitLength <> LQLength then
189-
Continue;
164+
LBytes[0] := (LBytes[0] and Byte($FF shr LExtraBits)) or Byte($80 shr LExtraBits);
165+
LBytes[System.Pred(LByteLength)] := LBytes[System.Pred(LByteLength)] or Byte(LLowBitsSet);
190166

191-
if not LQ.RabinMillerTest(2, ARandom, True) then
192-
Continue;
167+
LP := TBigInteger.Create(1, LBytes);
193168

194-
LP := LQ.ShiftLeft(1).Add(TBigInteger.One);
169+
LPMod3 := LP.&Mod(TBigInteger.Three).Int32ValueExact;
170+
if LPMod3 <> 2 then
171+
LP := LP.Add(TBigInteger.ValueOf((2 - LPMod3) * LInc3));
195172

196-
if not LP.RabinMillerTest(ACertainty, ARandom, True) then
197-
Continue;
173+
LCount := 0;
174+
while LCount < 256 do
175+
begin
176+
System.Inc(LCount);
177+
if LP.BitLength <> ABitLength then
178+
Break;
198179

199-
if (ACertainty > 2) and (not LQ.RabinMillerTest(ACertainty - 2, ARandom, True)) then
200-
Continue;
180+
if not HasAnySmallFactorsSafe(LP) then
181+
begin
182+
// NOTE: Pocklington criterion: Fermat test suffices to prove p prime given q is prime
183+
if FTwo.ModPow(LP, LP).Equals(FTwo) then
184+
begin
185+
LQ := LP.ShiftRight(1);
186+
if LQ.RabinMillerTest(ACertainty, ARandom, True) then
187+
begin
188+
if TWNafUtilities.GetNafWeight(LP) >= LMinWeight then
189+
begin
190+
Result := TCryptoLibGenericArray<TBigInteger>.Create(LP, LQ);
191+
Exit;
192+
end;
193+
end;
194+
end;
201195

202-
if TWNafUtilities.GetNafWeight(LP) < LMinWeight then
203-
Continue;
196+
Break;
197+
end;
204198

205-
Break;
199+
LP := LP.Add(LStep);
206200
end;
207201
end;
208-
209-
Result := TCryptoLibGenericArray<TBigInteger>.Create(LP, LQ);
210202
end;
211203

212-
{$IFNDEF _FIXINSIGHT_}
213-
204+
{
205+
// Select a high order element of the multiplicative group Zp*
206+
// (see generateSafePrimes). Superseded by fixed generator g = 2 when
207+
// GenerateSafePrimes is called with AForGenerator2 = true.
214208
class function TDHParametersHelper.SelectGenerator(const AP, AQ: TBigInteger;
215209
const ARandom: ISecureRandom): TBigInteger;
216210
var
@@ -225,6 +219,6 @@ class function TDHParametersHelper.SelectGenerator(const AP, AQ: TBigInteger;
225219
226220
Result := LG;
227221
end;
228-
{$ENDIF}
222+
}
229223

230224
end.

CryptoLib/src/Crypto/Generators/ClpRsaGenerators.pas

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,9 @@ function TRsaKeyPairGenerator.ChooseRandomPrime(ABitLength: Int32;
112112
(TArrayUtilities.Contains(FSpecialEValues, AE.Int32Value));
113113
while True do
114114
begin
115-
LP := TBigInteger.Create(ABitLength, 1, FParam.Random);
115+
LP := TBigIntegerUtilities.CreateRandomPrime(ABitLength, FParam.Certainty, FParam.Random);
116116
if LP.&Mod(AE).Equals(TBigInteger.One) then
117117
Continue;
118-
if not LP.IsProbablePrime(FParam.Certainty, True) then
119-
Continue;
120118
if not LEIsKnownOddPrime then
121119
begin
122120
LPSub1 := LP.Subtract(TBigInteger.One);
@@ -183,9 +181,8 @@ function TRsaKeyPairGenerator.GenerateKeyPair: IAsymmetricCipherKeyPair;
183181
LDP := LD.Remainder(LPSub1);
184182
LDQ := LD.Remainder(LQSub1);
185183
LQInv := TBigIntegerUtilities.ModOddInverse(LP, LQ);
186-
LPubKey := TRsaKeyParameters.Create(False, LN, LE) as IRsaKeyParameters;
187-
LPrivKey := TRsaPrivateCrtKeyParameters.Create(LN, LE, LD, LP, LQ, LDP, LDQ, LQInv)
188-
as IRsaPrivateCrtKeyParameters;
184+
LPubKey := TRsaKeyParameters.Create(False, LN, LE, True);
185+
LPrivKey := TRsaPrivateCrtKeyParameters.Create(LN, LE, LD, LP, LQ, LDP, LDQ, LQInv, True);
189186
Result := TAsymmetricCipherKeyPair.Create(LPubKey, LPrivKey);
190187
Exit;
191188
end;

0 commit comments

Comments
 (0)