xtool/contrib/fundamentals/Cipher/flcCipherRSA.pas

1966 lines
64 KiB
ObjectPascal
Raw Blame History

{******************************************************************************}
{ }
{ Library: Fundamentals 5.00 }
{ File name: flcCipherRSA.pas }
{ File version: 5.11 }
{ Description: RSA cipher routines }
{ }
{ Copyright: Copyright (c) 2008-2020, David J Butler }
{ All rights reserved. }
{ This file is licensed under the BSD License. }
{ See http://www.opensource.org/licenses/bsd-license.php }
{ Redistribution and use in source and binary forms, with }
{ or without modification, are permitted provided that }
{ the following conditions are met: }
{ Redistributions of source code must retain the above }
{ copyright notice, this list of conditions and the }
{ following disclaimer. }
{ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND }
{ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED }
{ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED }
{ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A }
{ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL }
{ THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, }
{ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR }
{ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, }
{ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF }
{ USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) }
{ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER }
{ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING }
{ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE }
{ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE }
{ POSSIBILITY OF SUCH DAMAGE. }
{ }
{ Github: https://github.com/fundamentalslib }
{ E-mail: fundamentals.library at gmail.com }
{ }
{ References: }
{ }
{ RFC 8017 - PKCS #1: RSA Cryptography Specifications Version 2.2 }
{ https://tools.ietf.org/html/rfc8017 }
{ }
{ }
{ Revision history: }
{ }
{ 2008/01/20 0.01 Initial development. }
{ 2010/08/04 0.02 Update for changes to HugeWord. }
{ 2010/08/04 0.03 EME_PKCS message encoding. }
{ 2010/08/09 0.04 EME_OAEP message encoding. }
{ 2010/08/10 4.05 Tests. }
{ 2010/12/01 4.06 RSAPublicKeyAssignBuf. }
{ 2015/03/31 4.07 RawByteString changes. }
{ 2015/06/08 4.08 RSASignMessage and RSACheckSignature. }
{ 2016/01/09 5.09 Revised for Fundamentals 5. }
{ 2018/07/17 5.10 Word32 changes. }
{ 2020/05/16 5.11 EMSA_PKCS1 message encoding. }
{ }
{******************************************************************************}
{$INCLUDE flcCipher.inc}
unit flcCipherRSA;
interface
uses
{ System }
SysUtils,
{ Fundamentals }
flcStdTypes,
flcHash,
flcHugeInt;
{ }
{ RSA }
{ }
type
ERSA = class(Exception);
TRSAPublicKey = record
KeyBits : Int32;
Modulus : HugeWord;
Exponent : HugeWord;
end;
TRSAPrivateKey = record
KeyBits : Int32;
Modulus : HugeWord;
Exponent : HugeWord; // d
PublicExponent : HugeWord; // e
Prime1 : HugeWord; // p
Prime2 : HugeWord; // q
Phi : HugeWord; // (p-1) * (q-1)
Exponent1 : HugeWord; // d mod (p - 1)
Exponent2 : HugeWord; // d mod (q - 1)
Coefficient : HugeWord; // (inverse of q) mod p
end;
TRSAMessage = HugeWord;
{ RSAPublicKey }
procedure RSAPublicKeyInit(var Key: TRSAPublicKey);
procedure RSAPublicKeyFinalise(var Key: TRSAPublicKey);
procedure RSAPublicKeyAssign(var KeyD: TRSAPublicKey; const KeyS: TRSAPublicKey);
procedure RSAPublicKeyAssignHex(var Key: TRSAPublicKey;
const KeyBits: Int32;
const HexMod, HexExp: String);
procedure RSAPublicKeyAssignBuf(var Key: TRSAPublicKey;
const KeyBits: Int32;
const ModBuf; const ModBufSize: Integer;
const ExpBuf; const ExpBufSize: Integer;
const ReverseByteOrder: Boolean);
procedure RSAPublicKeyAssignBufStr(var Key: TRSAPublicKey;
const KeyBits: Int32;
const ModBuf, ExpBuf: RawByteString);
{ RSAPrivateKey }
procedure RSAPrivateKeyInit(var Key: TRSAPrivateKey);
procedure RSAPrivateKeyFinalise(var Key: TRSAPrivateKey);
procedure RSAPrivateKeyAssign(var KeyD: TRSAPrivateKey; const KeyS: TRSAPrivateKey);
procedure RSAPrivateKeyAssignHex(var Key: TRSAPrivateKey;
const KeyBits: Int32;
const HexMod, HexExp: RawByteString);
procedure RSAPrivateKeyAssignBuf(var Key: TRSAPrivateKey;
const KeyBits: Int32;
const ModBuf; const ModBufSize: Integer;
const ExpBuf; const ExpBufSize: Integer;
const ReverseByteOrder: Boolean);
procedure RSAPrivateKeyAssignBufStr(var Key: TRSAPrivateKey;
const KeyBits: Int32;
const ModBuf, ExpBuf: RawByteString);
{ Generate }
type
TRSAGenerateCallback =
procedure (const CallbackData: NativeUInt; var Abort: Boolean);
ERSAGenerateAborted = class(ERSA);
procedure RSAGenerateKeys(const KeyBits: Integer;
var PrivateKey: TRSAPrivateKey;
var PublicKey: TRSAPublicKey;
const Callback: TRSAGenerateCallback = nil;
const CallbackData: NativeUInt = 0);
function RSACipherMessageBufSize(const KeySize: Integer): Integer;
{ EME_PKCS1 }
{ Used in RSAES-PKCS1-v1_5 }
procedure RSAEncodeMessageEME_PKCS1(
const KeyBits: Int32;
const Buf; const BufSize: Integer;
var EncodedMessage: TRSAMessage);
function RSADecodeMessageEME_PKCS1(
const KeyBits: Int32;
const EncodedMessage: HugeWord;
var Buf; const BufSize: Integer): Integer;
{ EME_OAEP }
{ Used in RSAES-OAEP }
procedure RSAEncodeMessageEME_OAEP(
const KeyBits: Int32;
const Buf; const BufSize: Integer;
var EncodedMessage: TRSAMessage);
function RSADecodeMessageEME_OAEP(
const KeyBits: Int32;
const EncodedMessage: HugeWord;
var Buf; const BufSize: Integer): Integer;
{ RSACipher Message }
function RSACipherMessageToBuf(
const KeyBits: Int32;
const CipherMessage: TRSAMessage;
var CipherBuf; const CipherBufSize: Integer): Integer;
procedure RSACipherBufToMessage(
const KeyBits: Int32;
const CipherBuf; const CipherBufSize: Integer;
var CipherMessage: TRSAMessage);
{ RSA Encryption Type }
type
TRSAEncryptionType = (
rsaetRSAES_PKCS1,
rsaetRSAES_OAEP
);
{ Encrypt }
procedure RSAEncryptMessage(
const PublicKey: TRSAPublicKey;
const PlainMessage: TRSAMessage;
var CipherMessage: TRSAMessage);
function RSAEncrypt(
const EncryptionType: TRSAEncryptionType;
const PublicKey: TRSAPublicKey;
const PlainBuf; const PlainBufSize: Integer;
var CipherBuf; const CipherBufSize: Integer): Integer;
function RSAEncryptStr(
const EncryptionType: TRSAEncryptionType;
const PublicKey: TRSAPublicKey;
const Plain: RawByteString): RawByteString;
{ Decrypt }
procedure RSADecryptMessage(
const PrivateKey: TRSAPrivateKey;
const CipherMessage: TRSAMessage;
var EncodedMessage: TRSAMessage);
function RSADecrypt(
const EncryptionType: TRSAEncryptionType;
const PrivateKey: TRSAPrivateKey;
const CipherBuf; const CipherBufSize: Integer;
var PlainBuf; const PlainBufSize: Integer): Integer;
function RSADecryptStr(
const EncryptionType: TRSAEncryptionType;
const PrivateKey: TRSAPrivateKey;
const Cipher: RawByteString): RawByteString;
{ Hash }
type
TRSAHashType = (
rsahfMD5,
rsahfSHA1,
rsahfSHA256,
rsahfSHA384,
rsahfSHA512
);
function RSAHashBuf(
const HashType: TRSAHashType;
const Buf; const BufSize: NativeInt;
const Digest; const DigestSize: Integer): Integer;
{ EMSA_PKCS1 }
procedure RSAEncodeMessageEMSA_PKCS1(
const KeyBits: Int32;
const HashType: TRSAHashType;
const Buf; const BufSize: NativeInt;
var EncodedMessage: TRSAMessage);
function RSADecodeMessageEMSA_PKCS1(
const KeyBits: Int32;
const EncodedMessage: HugeWord;
var HashType: TRSAHashType;
var Buf; const BufSize: Integer): Integer;
{ SignMsg }
procedure RSASignBufToMsg(const KeyBits: Integer; var Msg: HugeWord; const Buf; const BufSize: Integer);
function RSASignMsgToBuf(const KeyBits: Integer; var Buf; const BufSize: Integer; const Msg: HugeWord): Integer;
{ Sign / Verify }
type
TRSASignMessageType = (
rsastMessage,
rsastEMSA_PKCS1
);
function RSASignMessage(
const SignType: TRSASignMessageType;
const HashType: TRSAHashType;
const PrivateKey: TRSAPrivateKey;
const MessageBuf; const MessageBufSize: Integer;
var SignatureBuf; const SignatureBufSize: Integer): Integer;
function RSACheckSignature(
const SignType: TRSASignMessageType;
const PublicKey: TRSAPublicKey;
const MessageBuf; const MessageBufSize: NativeInt;
const SignatureBuf; const SignatureBufSize: Integer): Boolean;
{ }
{ Test }
{ }
{$IFDEF CIPHER_TEST}
procedure Test;
{$ENDIF}
{$IFDEF OS_WIN}
{$IFDEF CIPHER_PROFILE}
procedure Profile;
{$ENDIF}
{$ENDIF}
implementation
uses
{ System }
{$IFDEF OS_WIN}
{$IFDEF CIPHER_PROFILE}
Windows,
{$ENDIF}
{$ENDIF}
{ Fundamentals }
flcUtils,
flcRandom;
{ }
{ SecureClear }
{ }
procedure SecureHugeWordFinalise(var A: HugeWord);
begin
HugeWordClearAndFinalise(A);
end;
{ }
{ RSA }
{ }
const
SRSAInvalidKeySize = 'Invalid RSA key size';
SRSAInvalidBufferSize = 'Invalid RSA buffer size';
SRSAInvalidMessage = 'Invalid RSA message';
SRSAMessageTooLong = 'RSA message too long';
SRSAInvalidEncryptionType = 'Invalid RSA encryption type';
procedure RSAPublicKeyInit(var Key: TRSAPublicKey);
begin
Key.KeyBits := 0;
HugeWordInit(Key.Modulus);
HugeWordInit(Key.Exponent);
end;
procedure RSAPublicKeyFinalise(var Key: TRSAPublicKey);
begin
SecureHugeWordFinalise(Key.Exponent);
SecureHugeWordFinalise(Key.Modulus);
end;
procedure RSAPublicKeyAssign(var KeyD: TRSAPublicKey; const KeyS: TRSAPublicKey);
begin
KeyD.KeyBits := KeyS.KeyBits;
HugeWordAssign(KeyD.Modulus, KeyS.Modulus);
HugeWordAssign(KeyD.Exponent, KeyS.Exponent);
end;
procedure RSAPublicKeyAssignHex(var Key: TRSAPublicKey;
const KeyBits: Int32;
const HexMod, HexExp: String);
begin
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
Key.KeyBits := KeyBits;
HexToHugeWord(HexMod, Key.Modulus);
HexToHugeWord(HexExp, Key.Exponent);
if (HugeWordGetBitCount(Key.Modulus) > KeyBits) or
(HugeWordGetBitCount(Key.Exponent) > KeyBits) then
raise ERSA.Create(SRSAInvalidKeySize);
end;
procedure RSAPublicKeyAssignBuf(var Key: TRSAPublicKey;
const KeyBits: Int32;
const ModBuf; const ModBufSize: Integer;
const ExpBuf; const ExpBufSize: Integer;
const ReverseByteOrder: Boolean);
begin
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
Key.KeyBits := KeyBits;
HugeWordAssignBuf(Key.Modulus, ModBuf, ModBufSize, ReverseByteOrder);
HugeWordAssignBuf(Key.Exponent, ExpBuf, ExpBufSize, ReverseByteOrder);
end;
procedure RSAPublicKeyAssignBufStr(var Key: TRSAPublicKey;
const KeyBits: Int32;
const ModBuf, ExpBuf: RawByteString);
begin
RSAPublicKeyAssignBuf(Key, KeyBits, PByteChar(ModBuf)^, Length(ModBuf),
PByteChar(ExpBuf)^, Length(ExpBuf), True);
end;
procedure RSAPrivateKeyInit(var Key: TRSAPrivateKey);
begin
Key.KeyBits := 0;
HugeWordInit(Key.Modulus);
HugeWordInit(Key.Exponent);
HugeWordInit(Key.PublicExponent);
HugeWordInit(Key.Prime1);
HugeWordInit(Key.Prime2);
HugeWordInit(Key.Phi);
HugeWordInit(Key.Exponent1);
HugeWordInit(Key.Exponent2);
HugeWordInit(Key.Coefficient);
end;
procedure RSAPrivateKeyFinalise(var Key: TRSAPrivateKey);
begin
SecureHugeWordFinalise(Key.Coefficient);
SecureHugeWordFinalise(Key.Exponent2);
SecureHugeWordFinalise(Key.Exponent1);
SecureHugeWordFinalise(Key.Phi);
SecureHugeWordFinalise(Key.Prime2);
SecureHugeWordFinalise(Key.Prime1);
SecureHugeWordFinalise(Key.PublicExponent);
SecureHugeWordFinalise(Key.Exponent);
SecureHugeWordFinalise(Key.Modulus);
end;
procedure RSAPrivateKeyAssign(var KeyD: TRSAPrivateKey; const KeyS: TRSAPrivateKey);
begin
KeyD.KeyBits := KeyS.KeyBits;
HugeWordAssign(KeyD.Modulus, KeyS.Modulus);
HugeWordAssign(KeyD.Exponent, KeyS.Exponent);
HugeWordAssign(KeyD.PublicExponent, KeyS.PublicExponent);
HugeWordAssign(KeyD.Prime1, KeyS.Prime1);
HugeWordAssign(KeyD.Prime2, KeyS.Prime2);
HugeWordAssign(KeyD.Phi, KeyS.Phi);
HugeWordAssign(KeyD.Exponent1, KeyS.Exponent1);
HugeWordAssign(KeyD.Exponent2, KeyS.Exponent2);
HugeWordAssign(KeyD.Coefficient, KeyS.Coefficient);
end;
procedure RSAPrivateKeyAssignHex(var Key: TRSAPrivateKey;
const KeyBits: Int32;
const HexMod, HexExp: RawByteString);
begin
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
Key.KeyBits := KeyBits;
HexToHugeWordB(HexMod, Key.Modulus);
HexToHugeWordB(HexExp, Key.Exponent);
if (HugeWordGetBitCount(Key.Modulus) > KeyBits) or
(HugeWordGetBitCount(Key.Exponent) > KeyBits) then
raise ERSA.Create(SRSAInvalidKeySize);
end;
procedure RSAPrivateKeyAssignBuf(var Key: TRSAPrivateKey;
const KeyBits: Int32;
const ModBuf; const ModBufSize: Integer;
const ExpBuf; const ExpBufSize: Integer;
const ReverseByteOrder: Boolean);
begin
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
Key.KeyBits := KeyBits;
HugeWordAssignBuf(Key.Modulus, ModBuf, ModBufSize, ReverseByteOrder);
HugeWordAssignBuf(Key.Exponent, ExpBuf, ExpBufSize, ReverseByteOrder);
if (HugeWordGetBitCount(Key.Modulus) > KeyBits) or
(HugeWordGetBitCount(Key.Exponent) > KeyBits) then
raise ERSA.Create(SRSAInvalidKeySize);
end;
procedure RSAPrivateKeyAssignBufStr(var Key: TRSAPrivateKey;
const KeyBits: Int32;
const ModBuf, ExpBuf: RawByteString);
begin
RSAPrivateKeyAssignBuf(Key, KeyBits, PByteChar(ModBuf)^, Length(ModBuf),
PByteChar(ExpBuf)^, Length(ExpBuf), True);
end;
{ RSA Key Random Number }
{ Returns a random number for use in RSA key generation. }
procedure RSAKeyRandomNumber(const Bits: Integer; var A: HugeWord);
var
L : Integer;
begin
Assert(HugeWordElementBits >= 32);
if (Bits <= 0) or
(Bits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
// generate non-zero random number
L := Bits div HugeWordElementBits;
repeat
HugeWordRandom(A, L);
until not HugeWordIsZero(A);
// set least significant bit to make odd
HugeWordSetBit(A, 0);
// set one of the 15 most significant bits to ensure product is Bits * 2 large
// and this number allocates requested number of Bits in the HugeWord structure
HugeWordSetBit(A, Bits - RandomUniform(15) - 1);
// validate
Assert(HugeWordIsOdd(A));
Assert(HugeWordGetBitCount(A) = Bits);
end;
{ RSA Key Random Prime1 }
{ Returns the first of two random primes for use in RSA key generation. }
procedure RSAKeyRandomPrime1(const Bits: Integer; var P: HugeWord;
const Callback: TRSAGenerateCallback;
const CallbackData: NativeUInt);
var
Abort : Boolean;
begin
Abort := False;
repeat
RSAKeyRandomNumber(Bits, P);
// set the 2 most significant bits to:
// i) ensure that first prime is large enough so that there are
// enough smaller primes to choose from for the second prime;
// ii) the product is large enough
HugeWordSetBit(P, Bits - 1);
HugeWordSetBit(P, Bits - 2);
if Assigned(Callback) then
begin
Callback(CallbackData, Abort);
if Abort then
raise ERSAGenerateAborted.Create('Abort');
end;
until HugeWordIsPrime(P) <> pNotPrime;
end;
{ RSA Key Random Prime2 }
{ Returns the second of two random primes for use in RSA key generation. }
procedure RSAKeyRandomPrime2(const Bits: Integer; const P: HugeWord; var Q: HugeWord;
const Callback: TRSAGenerateCallback;
const CallbackData: NativeUInt);
var
Abort : Boolean;
L : HugeWord;
C : Integer;
N : Word32;
begin
Abort := False;
C := Bits div HugeWordElementBits;
HugeWordInit(L);
try
repeat
repeat
repeat
repeat
// choose a new random number with every iteration to maintain
// uniform distribution
RSAKeyRandomNumber(Bits, Q);
// "Numbers p and q should not be 'too close', lest the Fermat factorization for n be successful,
// if p - q, for instance is less than 2n^1/4 (which for even small 1024-bit values of n is 3<>10^77)
// solving for p and q is trivial"
HugeWordAssignOne(L);
HugeWordShl(L, (Bits div 4) + 1);
HugeWordAdd(L, Q);
until HugeWordCompare(P, L) > 0;
// ensure p > 2q - prevents certain attacks
HugeWordAssign(L, Q);
HugeWordShl1(L);
until HugeWordCompare(P, L) > 0;
// ensure N = P * Q large enough
N := Byte(HugeWordGetElement(P, C - 1) shr (HugeWordElementBits - 8)) *
Byte(HugeWordGetElement(Q, C - 1) shr (HugeWordElementBits - 8));
until N >= $0100;
if Assigned(Callback) then
begin
Callback(CallbackData, Abort);
if Abort then
raise ERSAGenerateAborted.Create('Abort');
end;
// ensure prime
until HugeWordIsPrime(Q) <> pNotPrime;
finally
SecureHugeWordFinalise(L);
end;
end;
{ RSA Key Random Prime Pair }
{ Returns a pair of random primes for use in RSA key generation. }
procedure RSAKeyRandomPrimePair(const Bits: Integer; var P, Q: HugeWord;
const Callback: TRSAGenerateCallback;
const CallbackData: NativeUInt);
begin
RSAKeyRandomPrime1(Bits, P, Callback, CallbackData);
RSAKeyRandomPrime2(Bits, P, Q, Callback, CallbackData);
end;
{ RSA Generate Keys }
{ Returns a randomly generated PrivateKey/PublicKey pair. }
const
RSAExpCount = 7;
RSAExp : array[0..RSAExpCount - 1] of Integer = (3, 5, 7, 11, 17, 257, 65537);
procedure RSAGenerateKeys(const KeyBits: Integer;
var PrivateKey: TRSAPrivateKey;
var PublicKey: TRSAPublicKey;
const Callback: TRSAGenerateCallback;
const CallbackData: NativeUInt);
var
Abort : Boolean;
Bits : Integer;
P, Q, N, E, D, G : HugeWord;
F, T : Word32;
R : Boolean;
begin
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
Abort := False;
HugeWordInit(P);
HugeWordInit(Q);
HugeWordInit(N);
HugeWordInit(E);
HugeWordInit(D);
HugeWordInit(G);
try
Bits := KeyBits div 2;
repeat
R := False;
repeat
// generate random prime values for p and q
RSAKeyRandomPrimePair(Bits, P, Q, Callback, CallbackData);
// calculate n = p * q
HugeWordMultiply(N, P, Q);
Assert(HugeWordGetBitCount(N) = KeyBits);
// save private key primes
HugeWordAssign(PrivateKey.Prime1, P);
HugeWordAssign(PrivateKey.Prime2, Q);
// calculate phi = (p-1) * (q-1)
HugeWordDec(P);
HugeWordDec(Q);
HugeWordMultiply(PrivateKey.Phi, P, Q);
// choose e such that 1 < e < phi and gcd(e, phi) = 1
// try 3 values for e before giving up
T := 0;
repeat
if Assigned(Callback) then
begin
Callback(CallbackData, Abort);
if Abort then
raise ERSAGenerateAborted.Create('Abort');
end;
Inc(T);
F := RSAExp[RandomUniform(RSAExpCount)];
HugeWordAssignWord32(E, F);
HugeWordGCD(E, PrivateKey.Phi, G);
if HugeWordIsOne(G) then
R := True;
until R or (T = 3);
until R;
// d = inverse(e) mod phi
until HugeWordModInv(E, PrivateKey.Phi, D);
// populate PrivateKey and PublicKey
PrivateKey.KeyBits := KeyBits;
HugeWordMod(D, P, PrivateKey.Exponent1); // d mod (p - 1)
HugeWordMod(D, Q, PrivateKey.Exponent2); // d mod (q - 1)
HugeWordAssign(PrivateKey.Modulus, N);
HugeWordAssign(PrivateKey.Exponent, D);
HugeWordAssign(PrivateKey.PublicExponent, E);
PublicKey.KeyBits := KeyBits;
HugeWordAssign(PublicKey.Modulus, N);
HugeWordAssign(PublicKey.Exponent, E);
finally
SecureHugeWordFinalise(G);
SecureHugeWordFinalise(D);
SecureHugeWordFinalise(E);
SecureHugeWordFinalise(N);
SecureHugeWordFinalise(Q);
SecureHugeWordFinalise(P);
end;
end;
{ RSA Cipher Message Buf Size }
function RSACipherMessageBufSize(const KeySize: Integer): Integer;
begin
Result := KeySize div 8;
end;
{ RSA Encode Message PKCS1 }
{ Encodes a message buffer as a RSA message. }
{ Uses EME-PKCS1-v1_5 encoding. }
{ EM = 0x00 || 0x02 || PS || 0x00 || M }
procedure RSAEncodeMessageEME_PKCS1(
const KeyBits: Int32;
const Buf; const BufSize: Integer;
var EncodedMessage: TRSAMessage);
var
N, L, I, C : Integer;
P, Q : PByte;
begin
// validate
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
// message size
N := KeyBits div 8; // number of bytes in key (max message size)
C := BufSize;
if C < 0 then
C := 0;
L := N - 3 - C; // number of padding bytes in PS
if L < 8 then
raise ERSA.Create(SRSAMessageTooLong);
HugeWordSetSize(EncodedMessage, N div HugeWordElementSize);
// 0x00
P := EncodedMessage.Data;
Inc(P, N - 1);
P^ := 0;
// 0x02
Dec(P);
P^ := 2;
// PS
Dec(P);
for I := 0 to L - 1 do
begin
P^ := RandomByteNonZero;
Dec(P);
end;
// 0x00
P^ := 0;
Dec(P);
// M
if C = 0 then
exit;
Q := @Buf;
for I := 0 to C - 1 do
begin
P^ := Q^;
Dec(P);
Inc(Q);
end;
end;
{ RSA OAEP MGF1 }
{ Mask generation function (MGF) function for OAEP encoding. }
{ This implements MGF1 from PKCS1v2-1 using SHA1 hashing. }
{ }
{ MGF1 (mgfSeed, maskLen) }
{ mgfSeed = seed from which mask is generated, an octet string }
{ maskLen = intended length in octets of the mask, at most 2^32 * hLen }
{ Hash = hash function }
{ hLen = length in octets of the hash function output }
{ mask = mask, an octet string of length maskLen }
{ Steps: }
{ 1. If maskLen > 2^32 * hLen, output <20>mask too long<6E> and stop. }
{ 2. Let T be the empty octet string. }
{ 3. For counter from 0 to [ maskLen / hLen ] <20> 1, do the following: }
{ a. Convert counter to an octet string C of length 4 octets }
{ C = I2OSP (counter, 4) }
{ b. Concatenate the hash of the seed mgfSeed and C to the octet string T }
{ T = T || Hash (mgfSeed || C) }
{ 4. Output the leading maskLen octets of T as the octet string mask. }
procedure RSAOAEPMGF1(
const SeedBuf; const SeedBufSize: Integer;
var MaskBuf; const MaskBufSize: Integer);
var
N, I, C, D, J : Integer;
HashStr : RawByteString;
HashSHA1 : T160BitDigest;
P, Q, R : PByte;
const
hLen = SizeOf(T160BitDigest);
begin
Assert(SeedBufSize > 0);
Assert(MaskBufSize > 0);
SetLength(HashStr, SeedBufSize + 4);
N := (MaskBufSize + hLen - 1) div hLen;
C := MaskBufSize;
P := @MaskBuf;
for I := 0 to N - 1 do
begin
// HashStr = mgfSeed || C
Move(SeedBuf, HashStr[1], SeedBufSize);
R := @HashStr[SeedBufSize + 1];
Q := @I;
Inc(Q, 3);
for J := 0 to 3 do
begin
R^ := Q^;
Inc(R);
Dec(Q);
end;
// HashSHA1 = Hash (mgfSeed || C)
HashSHA1 := CalcSHA1(HashStr);
// T = T || Hash (mgfSeed || C)
D := C;
if D > hLen then
D := hLen;
Move(HashSHA1, P^, D);
Inc(P, D);
Dec(C, D);
end;
end;
{ RSA Decode Message PKCS1 }
{ Decodes message previously encoded with RSAEncodeMessagePKCS1. }
{ Uses EME-PKCS1-v1_5 encoding. }
{ EM = 0x00 || 0x02 || PS || 0x00 || M }
{ Returns number of bytes needed to decode message. }
function RSADecodeMessageEME_PKCS1(
const KeyBits: Int32;
const EncodedMessage: HugeWord;
var Buf; const BufSize: Integer): Integer;
var
L, I : Integer;
P, Q : PByte;
begin
// validate
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
if HugeWordGetBitCount(EncodedMessage) <> KeyBits then
raise ERSA.Create(SRSAInvalidMessage);
// decode
L := HugeWordGetSize(EncodedMessage) * HugeWordElementSize;
if L < 3 then
raise ERSA.Create(SRSAInvalidMessage);
// 0x00
P := EncodedMessage.Data;
Inc(P, L - 1);
if P^ <> 0 then
raise ERSA.Create(SRSAInvalidMessage);
// 0x02
Dec(P);
if P^ <> 2 then
raise ERSA.Create(SRSAInvalidMessage);
Dec(L, 2);
// PS
if L < 9 then
raise ERSA.Create(SRSAInvalidMessage);
repeat
Dec(P);
Dec(L);
until (L = 0) or (P^ = 0);
// 0x00
if P^ <> 0 then
raise ERSA.Create(SRSAInvalidMessage);
// M
Result := L;
if L = 0 then
exit;
if BufSize = 0 then
exit;
Dec(P);
Q := @Buf;
for I := 0 to L - 1 do
begin
Q^ := P^;
if I >= BufSize then
exit;
Dec(P);
Inc(Q);
end;
end;
{ RSA XOR Buf }
procedure RSAXORBuf(
var Buf; const BufSize: Integer;
const MaskBuf; const MaskSize: Integer);
var
N, I, J, C : Integer;
P, Q : PByte;
begin
Assert(MaskSize > 0);
C := BufSize;
if C < 0 then
C := 0;
if C = 0 then
exit;
N := (C + MaskSize - 1) div MaskSize;
P := @Buf;
for I := 0 to N - 1 do
begin
Q := @MaskBuf;
for J := 0 to MaskSize - 1 do
begin
P^ := P^ xor Q^;
Inc(P);
Inc(Q);
Dec(C);
if C = 0 then
exit;
end;
end;
end;
{ RSA Encode Message OAEP }
{ Encodes a message buffer as a RSA message. }
{ Uses EME-OAEP encoding using SHA1 hashing. }
{ }
{ EME-OAEP-Encode(M, P,emLen) }
{ M = message to be encoded, length at most emLen - 2 - 2 * hLen }
{ mLen = length in octets of the message M }
{ hLen = length in octets of the hash function output }
{ PS = emLen - mLen - 2 * hLen - 2 zero octets }
{ P = encoding parameters, an octet string (default empty) }
{ pHash = Hash(P), an octet string of length hLen }
{ DB = pHash || PS || 01 || M }
{ seed = random octet string of length hLen }
{ dbMask = MGF(seed, emLen - hLen) }
{ maskedDB = DB x dbMask }
{ seedMask = MGF(maskedDB, hLen) }
{ maskedSeed = seed x seedMask }
{ EM = 0x00 || maskedSeed || maskedDB }
const
RSAOAEPHashBufSize = SizeOf(T160BitDigest);
{.DEFINE DEBUG_RSAFixedSeed}
procedure RSAEncodeMessageEME_OAEP(
const KeyBits: Int32;
const Buf; const BufSize: Integer;
var EncodedMessage: TRSAMessage);
var
mLen, emLen, psLen, dbMaskLen, dbLen, I : Integer;
seed, PS, dbMask, pHash, DB, maskedDB, seedMask, maskedSeed, EM : RawByteString;
P, Q : PByte;
const
hLen = RSAOAEPHashBufSize;
begin
// validate
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
// message size
emLen := KeyBits div 8; // number of bytes in key (max message size)
mLen := BufSize;
if mLen < 0 then
mLen := 0;
if mLen > emLen - 2 * hLen - 2 then
raise ERSA.Create(SRSAMessageTooLong);
HugeWordSetSize(EncodedMessage, emLen div HugeWordElementSize);
// pHash = Hash(P), an octet string of length hLen
// SetLength(pHash, hLen);
// HashP := CalcSHA1('');
// Move(HashP, pHash[1], hLen);
pHash := RawByteString(
#$DA#$39#$A3#$EE#$5E#$6B#$4B#$0D#$32#$55 +
#$BF#$EF#$95#$60#$18#$90#$AF#$D8#$07#$09); ////
// seed = random octet string of length hLen
{$IFDEF DEBUG_RSAFixedSeed}
seed := RawByteString(
#$aa#$fd#$12#$f6#$59#$ca#$e6#$34#$89#$b4 +
#$79#$e5#$07#$6d#$de#$c2#$f0#$6c#$b5#$8f);
{$ELSE}
SetLength(seed, hLen);
for I := 1 to hLen do
seed[I] := AnsiChar(RandomByteNonZero);
{$ENDIF}
// PS = emLen - mLen - 2 * hLen - 2 zero octets
psLen := emLen - mLen - 2 * hLen - 2;
SetLength(PS, psLen);
for I := 1 to psLen do
PS[I] := #0;
// dbMask = MGF(seed, emLen - hLen - 1)
dbMaskLen := emLen - hLen - 1;
SetLength(dbMask, dbMaskLen);
RSAOAEPMGF1(seed[1], hLen, dbMask[1], dbMaskLen);
// DB = pHash || PS || 01 || M
dbLen := hLen + psLen + 1 + mLen;
SetLength(DB, dbLen);
P := @DB[1];
Move(pHash[1], P^, hLen);
Inc(P, hLen);
Move(PS[1], P^, psLen);
Inc(P, psLen);
P^ := 1;
Inc(P);
Move(Buf, P^, mLen);
// maskedDB = DB x dbMask
SetLength(maskedDB, dbLen);
Move(DB[1], maskedDB[1], dbLen);
RSAXORBuf(maskedDB[1], dbLen, dbMask[1], dbMaskLen);
// seedMask = MGF(maskedDB, hLen)
SetLength(seedMask, hLen);
RSAOAEPMGF1(maskedDB[1], dbLen, seedMask[1], hLen);
// maskedSeed = seed x seedMask
SetLength(maskedSeed, hLen);
Move(seed[1], maskedSeed[1], hLen);
RSAXORBuf(maskedSeed[1], hLen, seedMask[1], hLen);
// EM = 0x00 || maskedSeed || maskedDB
SetLength(EM, emLen);
P := @EM[1];
P^ := 0;
Inc(P);
Move(maskedSeed[1], P^, hLen);
Inc(P, hLen);
Move(maskedDB[1], P^, dbLen);
// populate message
P := EncodedMessage.Data;
Inc(P, emLen - 1);
Q := @EM[1];
for I := 0 to emLen - 1 do
begin
P^ := Q^;
Dec(P);
Inc(Q);
end;
end;
{ RSA Decode Message OAEP }
{ Decodes message previously encoded with RSAEncodeMessageOAEP. }
{ Uses EME-OAEP encoding using SHA1 hashing. }
{ }
{ EME-OAEP-Encode(M, P,emLen) }
{ M = message to be encoded, length at most emLen - 2 - 2h * Len }
{ mLen = length in octets of the message M }
{ hLen = length in octets of the hash function output }
{ PS = emLen - mLen - 2 * hLen - 2 zero octets }
{ P = encoding parameters, an octet string (default empty) }
{ pHash = Hash(P), an octet string of length hLen }
{ DB = pHash || PS || 01 || M }
{ seed = random octet string of length hLen }
{ dbMask = MGF(seed, emLen - hLen) }
{ maskedDB = DB x dbMask }
{ seedMask = MGF(maskedDB, hLen) }
{ maskedSeed = seed x seedMask }
{ EM = 0x00 || maskedSeed || maskedDB }
function RSADecodeMessageEME_OAEP(
const KeyBits: Int32;
const EncodedMessage: HugeWord;
var Buf; const BufSize: Integer): Integer;
var
I, L, emLen, dbLen : Integer;
maskedSeed, maskedDB, seedMask, seed, dbMask, DB, pHash, pHashPr : RawByteString;
P, Q : PByte;
const
hLen = RSAOAEPHashBufSize;
begin
// validate
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
if HugeWordGetBitCount(EncodedMessage) <> KeyBits then
raise ERSA.Create(SRSAInvalidMessage);
// decode
emLen := HugeWordGetSize(EncodedMessage) * HugeWordElementSize;
// EM = 0x00 || maskedSeed || maskedDB
dbLen := emLen - hLen - 1;
SetLength(maskedSeed, hLen);
SetLength(maskedDB, dbLen);
P := EncodedMessage.Data;
Inc(P, emLen - 1);
if P^ <> 0 then
raise ERSA.Create(SRSAInvalidMessage);
Dec(P);
Q := @maskedSeed[1];
for I := 0 to hLen - 1 do
begin
Q^ := P^;
Dec(P);
Inc(Q);
end;
Q := @maskedDB[1];
for I := 0 to dbLen - 1 do
begin
Q^ := P^;
Dec(P);
Inc(Q);
end;
// Let seedMask = MGF(maskedDB, hLen)
SetLength(seedMask, hLen);
RSAOAEPMGF1(maskedDB[1], dbLen, seedMask[1], hLen);
// Let seed = maskedSeed xor seedMask
SetLength(seed, hLen);
Move(maskedSeed[1], seed[1], hLen);
RSAXORBuf(seed[1], hLen, seedMask[1], hLen);
// Let dbMask = MGF(seed, ||EM|| - hLen)
SetLength(dbMask, dbLen);
RSAOAEPMGF1(seed[1], hLen, dbMask[1], dbLen);
// Let DB = maskedDB xor dbMask.
SetLength(DB, dbLen);
Move(maskedDB[1], DB[1], dbLen);
RSAXORBuf(DB[1], dbLen, dbMask[1], dbLen);
// Let pHash = Hash(P), an octet string of length hLen
// SetLength(pHash, hLen);
// Hash := CalcSHA1('');
// Move(Hash, pHash[1], hLen);
pHash := RawByteString(
#$DA#$39#$A3#$EE#$5E#$6B#$4B#$0D#$32#$55 +
#$BF#$EF#$95#$60#$18#$90#$AF#$D8#$07#$09);
// DB = pHash' || PS || 01 || M
// Decode pHash'
SetLength(pHashPr, hLen);
Move(DB[1], pHashPr[1], hLen);
if pHashPr <> pHash then
raise ERSA.Create(SRSAInvalidMessage);
// Decode PS || 01
I := hLen + 1;
while I <= dbLen do
case Byte(DB[I]) of
0 : Inc(I);
1 : break;
else
raise ERSA.Create(SRSAInvalidMessage);
end;
if I > dbLen then
raise ERSA.Create(SRSAInvalidMessage);
if Byte(DB[I]) <> 1 then
raise ERSA.Create(SRSAInvalidMessage);
Inc(I);
// Decode M
L := dbLen - I + 1;
Result := L;
if L > BufSize then
L := BufSize;
if L > 0 then
Move(DB[I], Buf, L);
end;
{ RSA Cipher Message To Buf }
{ Copies cipher message to buffer. }
{ Returns the buffer size required for the message. }
function RSACipherMessageToBuf(
const KeyBits: Int32;
const CipherMessage: TRSAMessage;
var CipherBuf; const CipherBufSize: Integer): Integer;
var
L, I : Integer;
P, Q : PByte;
begin
if HugeWordGetBitCount(CipherMessage) <> KeyBits then
raise ERSA.Create(SRSAInvalidMessage);
L := KeyBits div 8;
Result := L;
if CipherBufSize <= 0 then
exit;
P := CipherMessage.Data;
Inc(P, L - 1);
Q := @CipherBuf;
for I := 0 to L - 1 do
begin
if I >= CipherBufSize then
exit;
Q^ := P^;
Inc(Q);
Dec(P);
end;
end;
{ RSA Cipher Buf To Message }
procedure RSACipherBufToMessage(
const KeyBits: Int32;
const CipherBuf; const CipherBufSize: Integer;
var CipherMessage: TRSAMessage);
var
L, I : Integer;
P, Q : PByte;
begin
// validate
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
// message size
L := KeyBits div 8;
if CipherBufSize <> L then
raise ERSA.Create(SRSAInvalidBufferSize);
HugeWordSetSize(CipherMessage, L div HugeWordElementSize);
// move data
P := CipherMessage.Data;
Inc(P, L - 1);
Q := @CipherBuf;
for I := 0 to L - 1 do
begin
P^ := Q^;
Dec(P);
Inc(Q);
end;
end;
{ RSA Encrypt Message }
procedure RSAEncryptMessage(
const PublicKey: TRSAPublicKey;
const PlainMessage: TRSAMessage;
var CipherMessage: TRSAMessage);
begin
// validate
if (PublicKey.KeyBits <= 0) or
(PublicKey.KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
if HugeWordCompare(PlainMessage, PublicKey.Modulus) >= 0 then
raise ERSA.Create(SRSAInvalidMessage);
Assert(HugeWordGetBitCount(PlainMessage) = PublicKey.KeyBits);
// encrypt
HugeWordPowerAndMod(CipherMessage, PlainMessage, PublicKey.Exponent, PublicKey.Modulus);
Assert(HugeWordGetBitCount(CipherMessage) = PublicKey.KeyBits);
end;
{ RSA Encrypt }
function RSAEncrypt(
const EncryptionType: TRSAEncryptionType;
const PublicKey: TRSAPublicKey;
const PlainBuf; const PlainBufSize: Integer;
var CipherBuf; const CipherBufSize: Integer): Integer;
var
EncodedMsg, CipherMsg : HugeWord;
begin
// validate
if (PublicKey.KeyBits <= 0) or
(PublicKey.KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
if (PlainBufSize < 0) or
(CipherBufSize <= 0) then
raise ERSA.Create(SRSAInvalidBufferSize);
// encrypt
HugeWordInit(EncodedMsg);
HugeWordInit(CipherMsg);
try
case EncryptionType of
rsaetRSAES_PKCS1 : RSAEncodeMessageEME_PKCS1(PublicKey.KeyBits, PlainBuf, PlainBufSize, EncodedMsg);
rsaetRSAES_OAEP : RSAEncodeMessageEME_OAEP(PublicKey.KeyBits, PlainBuf, PlainBufSize, EncodedMsg);
else
raise ERSA.Create(SRSAInvalidEncryptionType);
end;
RSAEncryptMessage(PublicKey, EncodedMsg, CipherMsg);
Result := RSACipherMessageToBuf(PublicKey.KeyBits, CipherMsg, CipherBuf, CipherBufSize);
if Result > CipherBufSize then
raise ERSA.Create(SRSAInvalidBufferSize);
finally
SecureHugeWordFinalise(CipherMsg);
SecureHugeWordFinalise(EncodedMsg);
end;
end;
{ RSA Encrypt Str }
function RSAEncryptStr(
const EncryptionType: TRSAEncryptionType;
const PublicKey: TRSAPublicKey;
const Plain: RawByteString): RawByteString;
var
L : Integer;
begin
L := RSACipherMessageBufSize(PublicKey.KeyBits);
SetLength(Result, L);
L := RSAEncrypt(EncryptionType, PublicKey, PByteChar(Plain)^, Length(Plain),
PByteChar(Result)^, L);
SetLength(Result, L);
end;
{ RSA Decrypt Message }
{ Decrypts using m = c^d mod n }
procedure RSADecryptMessage(
const PrivateKey: TRSAPrivateKey;
const CipherMessage: TRSAMessage;
var EncodedMessage: TRSAMessage);
begin
// validate
if (PrivateKey.KeyBits <= 0) or
(PrivateKey.KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
if HugeWordGetBitCount(CipherMessage) <> PrivateKey.KeyBits then
raise ERSA.Create(SRSAInvalidMessage);
// decrypt
HugeWordPowerAndMod(EncodedMessage, CipherMessage, PrivateKey.Exponent, PrivateKey.Modulus);
end;
{
Alternative decryption algorithm (works with smaller factors):
if the second form (p, q, dP, dQ,qInv) of K is used:
Let m1 = c^dP mod p
Let m2 = c^dQ mod q
Let h = (m1 - m2) . qInv mod p
Let m = m2 + q . h
}
procedure RSADecryptMessage2(
const PrivateKey: TRSAPrivateKey;
const CipherMessage: TRSAMessage;
var EncodedMessage: TRSAMessage);
var
A, M1, M2, H, M : HugeWord;
begin
HugeWordInit(A);
HugeWordInit(M1);
HugeWordInit(M2);
HugeWordInit(H);
try
// m1 = c^dP mod p
HugeWordPowerAndMod(M1, CipherMessage, PrivateKey.Exponent1, PrivateKey.Prime1);
// m2 = c^dQ mod q
HugeWordPowerAndMod(M2, CipherMessage, PrivateKey.Exponent2, PrivateKey.Prime2);
// h = (m1 - m2) . qInv mod p
HugeWordAssign(H, M1);
HugeWordSubtract(H, M2);
HugeWordMultiply(H, H, PrivateKey.Coefficient);
// m = m2 + q . h
HugeWordMultiply(M, PrivateKey.Prime2, H);
HugeWordAdd(M, M2);
finally
SecureHugeWordFinalise(H);
SecureHugeWordFinalise(M2);
SecureHugeWordFinalise(M1);
SecureHugeWordFinalise(A);
end;
end;
{ RSA Decrypt }
function RSADecrypt(
const EncryptionType: TRSAEncryptionType;
const PrivateKey: TRSAPrivateKey;
const CipherBuf; const CipherBufSize: Integer;
var PlainBuf; const PlainBufSize: Integer): Integer;
var
CipherMsg, EncodedMsg : HugeWord;
begin
// validate
if (PrivateKey.KeyBits <= 0) or
(PrivateKey.KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
// decrypt
HugeWordInit(CipherMsg);
HugeWordInit(EncodedMsg);
try
RSACipherBufToMessage(PrivateKey.KeyBits, CipherBuf, CipherBufSize, CipherMsg);
RSADecryptMessage(PrivateKey, CipherMsg, EncodedMsg);
case EncryptionType of
rsaetRSAES_PKCS1 : Result := RSADecodeMessageEME_PKCS1(PrivateKey.KeyBits, EncodedMsg, PlainBuf, PlainBufSize);
rsaetRSAES_OAEP : Result := RSADecodeMessageEME_OAEP(PrivateKey.KeyBits, EncodedMsg, PlainBuf, PlainBufSize);
else
raise ERSA.Create(SRSAInvalidEncryptionType);
end;
finally
SecureHugeWordFinalise(EncodedMsg);
SecureHugeWordFinalise(CipherMsg);
end;
end;
{ RSA Decrypt Str }
function RSADecryptStr(
const EncryptionType: TRSAEncryptionType;
const PrivateKey: TRSAPrivateKey;
const Cipher: RawByteString): RawByteString;
var
L, N : Integer;
begin
L := Length(Cipher);
if L = 0 then
raise ERSA.Create(SRSAInvalidMessage);
N := RSACipherMessageBufSize(PrivateKey.KeyBits);
SetLength(Result, N);
N := RSADecrypt(EncryptionType, PrivateKey, PByteChar(Cipher)^, L, PByteChar(Result)^, N);
SetLength(Result, N);
end;
{ EMSA_PKCS1 }
function GetRSAHashTypeDigestInfo(const HashType: TRSAHashType): TBytes;
begin
case HashType of
rsahfMD5 : Result := TBytes.Create($30, $20, $30, $0c, $06, $08, $2a, $86, $48, $86, $f7, $0d, $02, $05, $05, $00, $04, $10);
rsahfSHA1 : Result := TBytes.Create($30, $21, $30, $09, $06, $05, $2b, $0e, $03, $02, $1a, $05, $00, $04, $14);
rsahfSHA256 : Result := TBytes.Create($30, $31, $30, $0d, $06, $09, $60, $86, $48, $01, $65, $03, $04, $02, $01, $05, $00, $04, $20);
rsahfSHA384 : Result := TBytes.Create($30, $41, $30, $0d, $06, $09, $60, $86, $48, $01, $65, $03, $04, $02, $02, $05, $00, $04, $30);
rsahfSHA512 : Result := TBytes.Create($30, $51, $30, $0d, $06, $09, $60, $86, $48, $01, $65, $03, $04, $02, $03, $05, $00, $04, $40);
else
Result := nil;
end;
end;
const
RSAHashTypeDigestSize : array[TRSAHashType] of Int32 = (
16,
20,
32,
48,
64
);
function RSAHashBuf(
const HashType: TRSAHashType;
const Buf; const BufSize: NativeInt;
const Digest; const DigestSize: Integer): Integer;
var
DigSize : Int32;
begin
DigSize := RSAHashTypeDigestSize[HashType];
if DigestSize < DigSize then
raise ERSA.Create('Invalid digest buffer size');
case HashType of
rsahfMD5 : P128BitDigest(@Digest)^ := CalcMD5(Buf, BufSize);
rsahfSHA1 : P160BitDigest(@Digest)^ := CalcSHA1(Buf, BufSize);
rsahfSHA256 : P256BitDigest(@Digest)^ := CalcSHA256(Buf, BufSize);
rsahfSHA384 : P384BitDigest(@Digest)^ := CalcSHA384(Buf, BufSize);
rsahfSHA512 : P512BitDigest(@Digest)^ := CalcSHA512(Buf, BufSize);
else
raise ERSA.Create('Invalid hash type');
end;
Result := DigSize;
end;
procedure RSAEncodeMessageEMSA_PKCS1(
const KeyBits: Int32;
const HashType: TRSAHashType;
const Buf; const BufSize: NativeInt;
var EncodedMessage: TRSAMessage);
var
KeySize : Integer;
H : T512BitDigest;
T : TBytes;
InfoSize : Integer;
TLen : Integer;
HshSize : Integer;
P : PByte;
I : Integer;
N : Integer;
begin
// validate
if (KeyBits <= 0) or
(KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
// hash: H = Hash(M)
HshSize := RSAHashBuf(HashType, Buf, BufSize, H, SizeOf(H));
// DigestInfo ::= SEQUENCE { digestAlgorithm AlgorithmIdentifier, digest OCTET STRING }
T := GetRSAHashTypeDigestInfo(HashType);
InfoSize := Length(T);
TLen := InfoSize + HshSize;
SetLength(T, TLen);
Move(H, T[InfoSize], HshSize);
// EM = 0x00 || 0x01 || PS || 0x00 || T
KeySize := KeyBits div 8;
HugeWordSetSize(EncodedMessage, KeyBits div HugeWordElementBits);
P := EncodedMessage.Data;
Inc(P, KeySize - 1);
// 0x00
P^ := 0;
Dec(P);
// 0x01
P^ := 1;
Dec(P);
// PS
N := KeySize - 3 - TLen;
if N < 0 then
raise ERSA.Create('Invalid key size');
for I := 0 to N - 1 do
begin
P^ := $FF;
Dec(P);
end;
// 0x00
P^ := 0;
Dec(P);
// T
for I := 0 to TLen - 1 do
begin
P^ := T[I];
Dec(P);
end;
end;
function RSADecodeMessageEMSA_PKCS1(
const KeyBits: Int32;
const EncodedMessage: HugeWord;
var HashType: TRSAHashType;
var Buf; const BufSize: Integer): Integer;
var
KeySize : Integer;
N : Integer;
P, Q, R : PByte;
Hsh : TRSAHashType;
HshPre : TBytes;
HshSiz : Integer;
I, L : Integer;
Fnd : Boolean;
begin
KeySize := KeyBits div 8;
P := EncodedMessage.Data;
N := KeySize;
if EncodedMessage.Used < KeySize div HugeWordElementSize then
raise ERSA.Create(SRSAInvalidMessage);
Inc(P, KeySize - 1);
// EM = 0x00 || 0x01 || PS || 0x00 || T
if P^ <> 0 then
raise ERSA.Create(SRSAInvalidMessage);
Dec(P);
Dec(N);
if P^ <> 1 then
raise ERSA.Create(SRSAInvalidMessage);
Dec(P);
Dec(N);
while (N > 0) and (P^ = $FF) do
begin
Dec(P);
Dec(N);
end;
if N = 0 then
raise ERSA.Create(SRSAInvalidMessage);
if P^ <> 0 then
raise ERSA.Create(SRSAInvalidMessage);
Dec(P);
Dec(N);
if N = 0 then
raise ERSA.Create(SRSAInvalidMessage);
// Check hash id
for Hsh := Low(TRSAHashType) to High(TRSAHashType) do
begin
HshPre := GetRSAHashTypeDigestInfo(Hsh);
if N > Length(HshPre) then
begin
Q := P;
Fnd := True;
for I := 0 to Length(HshPre) - 1 do
if Q^ <> HshPre[I] then
begin
Fnd := False;
break;
end
else
Dec(Q);
if Fnd then
begin
HashType := Hsh;
Dec(N, Length(HshPre));
HshSiz := RSAHashTypeDigestSize[Hsh];
Dec(N, HshSiz);
if N < 0 then
raise ERSA.Create(SRSAInvalidMessage);
// move hash digest to Buf
if HshSiz < BufSize then
L := HshSiz
else
L := BufSize;
R := @Buf;
for I := 0 to L - 1 do
begin
R^ := Q^;
Inc(R);
Dec(Q);
end;
Result := KeySize - N;
exit;
end;
end;
end;
raise ERSA.Create('Invalid hash id');
end;
{ RSA Sign Message }
procedure RSASignBufToMsg(const KeyBits: Integer; var Msg: HugeWord; const Buf; const BufSize: Integer);
var
L, I : Integer;
P, Q : PByte;
begin
Assert(KeyBits > 0);
L := KeyBits div 8;
if BufSize > L then
raise ERSA.Create(SRSAInvalidBufferSize);
HugeWordSetSize(Msg, L div HugeWordElementSize);
P := Msg.Data;
FillChar(P^, L, 0);
Inc(P, L - 1);
Q := @Buf;
for I := 0 to BufSize - 1 do
begin
P^ := Q^;
Dec(P);
Inc(Q);
end;
end;
function RSASignMsgToBuf(const KeyBits: Integer; var Buf; const BufSize: Integer; const Msg: HugeWord): Integer;
var
L, N, I, C : Integer;
P, Q : PByte;
begin
L := KeyBits div 8;
if BufSize < L then
raise ERSA.Create(SRSAInvalidBufferSize);
N := Msg.Used * HugeWordElementSize;
P := Msg.Data;
C := MinInt(N, L);
if C > BufSize then
raise ERSA.Create(SRSAInvalidBufferSize);
Inc(P, C - 1); //// was N - 1
Q := @Buf;
for I := 0 to C - 1 do
begin
Q^ := P^;
Dec(P);
Inc(Q);
end;
Result := C;
end;
function RSASignMessage(
const SignType: TRSASignMessageType;
const HashType: TRSAHashType;
const PrivateKey: TRSAPrivateKey;
const MessageBuf; const MessageBufSize: Integer;
var SignatureBuf; const SignatureBufSize: Integer): Integer;
var
CipherMsg, EncodedMsg : HugeWord;
L : Integer;
begin
// validate
if (PrivateKey.KeyBits <= 0) or
(PrivateKey.KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
L := PrivateKey.KeyBits div 8;
if SignatureBufSize < L then
raise ERSA.Create(SRSAInvalidBufferSize);
// sign
HugeWordInit(CipherMsg);
HugeWordInit(EncodedMsg);
try
case SignType of
rsastMessage : RSASignBufToMsg(PrivateKey.KeyBits, CipherMsg, MessageBuf, MessageBufSize);
rsastEMSA_PKCS1 : RSAEncodeMessageEMSA_PKCS1(PrivateKey.KeyBits, HashType, MessageBuf, MessageBufSize, CipherMsg);
else
raise ERSA.Create('Invalid message type');
end;
HugeWordPowerAndMod(EncodedMsg, CipherMsg, PrivateKey.Exponent, PrivateKey.Modulus);
RSASignMsgToBuf(PrivateKey.KeyBits, SignatureBuf, SignatureBufSize, EncodedMsg);
finally
SecureHugeWordFinalise(EncodedMsg);
SecureHugeWordFinalise(CipherMsg);
end;
Result := L;
end;
function RSACheckSignature(
const SignType: TRSASignMessageType;
const PublicKey: TRSAPublicKey;
const MessageBuf; const MessageBufSize: NativeInt;
const SignatureBuf; const SignatureBufSize: Integer): Boolean;
var
L, C : Integer;
RMsgBuf : Pointer;
RMsgBufSize : Integer;
EncodedMsg, CipherMsg : HugeWord;
HashType : TRSAHashType;
Digest : T512BitDigest;
DigSize : Integer;
begin
// validate
if (PublicKey.KeyBits <= 0) or
(PublicKey.KeyBits mod HugeWordElementBits <> 0) then
raise ERSA.Create(SRSAInvalidKeySize);
L := PublicKey.KeyBits div 8;
if SignatureBufSize < L then
raise ERSA.Create(SRSAInvalidBufferSize);
// check signature
RMsgBufSize := L;
GetMem(RMsgBuf, RMsgBufSize);
try
HugeWordInit(EncodedMsg);
HugeWordInit(CipherMsg);
try
RSASignBufToMsg(PublicKey.KeyBits, CipherMsg, SignatureBuf, SignatureBufSize);
HugeWordPowerAndMod(EncodedMsg, CipherMsg, PublicKey.Exponent, PublicKey.Modulus);
case SignType of
rsastMessage :
begin
C := RSASignMsgToBuf(PublicKey.KeyBits, RMsgBuf^, RMsgBufSize, EncodedMsg);
Result := EqualMem(RMsgBuf^, MessageBuf, MinInt(C, MessageBufSize));
end;
rsastEMSA_PKCS1 :
begin
RSADecodeMessageEMSA_PKCS1(PublicKey.KeyBits, EncodedMsg, HashType, RMsgBuf^, RMsgBufSize);
DigSize := RSAHashBuf(HashType, MessageBuf, MessageBufSize, Digest, SizeOf(Digest));
Result := EqualMem(Digest, RMsgBuf^, DigSize);
end;
else
raise ERSA.Create('Invalid message type');
end;
finally
SecureHugeWordFinalise(CipherMsg);
SecureHugeWordFinalise(EncodedMsg);
end;
finally
FreeMem(RMsgBuf);
end;
end;
{ }
{ Test }
{ }
{$IFDEF CIPHER_TEST}
{$ASSERTIONS ON}
procedure Test;
procedure TestMGF;
var Seed, Mask : RawByteString;
begin
Seed :=
RawByteString(
#$aa#$fd#$12#$f6#$59#$ca#$e6#$34#$89#$b4 +
#$79#$e5#$07#$6d#$de#$c2#$f0#$6c#$b5#$8f);
SetLength(Mask, 107);
FillChar(Mask[1], 107, 0);
RSAOAEPMGF1(Seed[1], 20, Mask[1], 107);
Assert(Mask =
#$06#$e1#$de#$b2#$36#$9a#$a5#$a5#$c7#$07#$d8#$2c#$8e#$4e#$93#$24 +
#$8a#$c7#$83#$de#$e0#$b2#$c0#$46#$26#$f5#$af#$f9#$3e#$dc#$fb#$25 +
#$c9#$c2#$b3#$ff#$8a#$e1#$0e#$83#$9a#$2d#$db#$4c#$dc#$fe#$4f#$f4 +
#$77#$28#$b4#$a1#$b7#$c1#$36#$2b#$aa#$d2#$9a#$b4#$8d#$28#$69#$d5 +
#$02#$41#$21#$43#$58#$11#$59#$1b#$e3#$92#$f9#$82#$fb#$3e#$87#$d0 +
#$95#$ae#$b4#$04#$48#$db#$97#$2f#$3a#$c1#$4e#$af#$f4#$9c#$8c#$3b +
#$7c#$fc#$95#$1a#$51#$ec#$d1#$dd#$e6#$12#$64);
end;
procedure TestEncrypt;
var EM, CM : TRSAMessage;
Pub : TRSAPublicKey;
begin
HugeWordInit(EM);
HugeWordInit(CM);
RSAPublicKeyInit(Pub);
RSAPublicKeyAssignHex(Pub,
1024,
'bbf82f090682ce9c2338ac2b9da871f7' +
'368d07eed41043a440d6b6f07454f51f' +
'b8dfbaaf035c02ab61ea48ceeb6fcd48' +
'76ed520d60e1ec4619719d8a5b8b807f' +
'afb8e0a3dfc737723ee6b4b7d93a2584' +
'ee6a649d060953748834b2454598394e' +
'e0aab12d7b61a51f527a9a41f6c1687f' +
'e2537298ca2a8f5946f8e5fd091dbdcb',
'00000011');
HexToHugeWordB(
'00EB7A19ACE9E3006350E329504B45E2CA82310B26DCD87D5C68F1EEA8F55267C31B2E8BB4251F84' +
'D7E0B2C04626F5AFF93EDCFB25C9C2B3FF8AE10E839A2DDB4CDCFE4FF47728B4A1B7C1362BAAD29A' +
'B48D2869D5024121435811591BE392F982FB3E87D095AEB40448DB972F3AC14F7BC275195281CE32' +
'D2F1B76D4D353E2D', EM);
RSAEncryptMessage(Pub, EM, CM);
Assert(HugeWordToHexB(CM) =
'1253E04DC0A5397BB44A7AB87E9BF2A039A33D1E996FC82A94CCD30074C95DF763722017069E5268' +
'DA5D1C0B4F872CF653C11DF82314A67968DFEAE28DEF04BB6D84B1C31D654A1970E5783BD6EB96A0' +
'24C2CA2F4A90FE9F2EF5C9C140E5BB48DA9536AD8700C84FC9130ADEA74E558D51A74DDF85D8B50D' +
'E96838D6063E0955');
RSAPublicKeyFinalise(Pub);
HugeWordFinalise(CM);
HugeWordFinalise(EM);
end;
var Pri : TRSAPrivateKey;
Pub : TRSAPublicKey;
procedure TestStr(const EncryptionType: TRSAEncryptionType; const Pln: RawByteString);
var Enc, Dec : RawByteString;
begin
Enc := RSAEncryptStr(EncryptionType, Pub, Pln);
Assert(Enc <> Pln);
Dec := RSADecryptStr(EncryptionType, Pri, Enc);
Assert(Dec = Pln);
end;
procedure TestCase1;
begin
RSAPrivateKeyAssignHex(Pri,
1024,
'bbf82f090682ce9c2338ac2b9da871f7' +
'368d07eed41043a440d6b6f07454f51f' +
'b8dfbaaf035c02ab61ea48ceeb6fcd48' +
'76ed520d60e1ec4619719d8a5b8b807f' +
'afb8e0a3dfc737723ee6b4b7d93a2584' +
'ee6a649d060953748834b2454598394e' +
'e0aab12d7b61a51f527a9a41f6c1687f' +
'e2537298ca2a8f5946f8e5fd091dbdcb',
'a5dafc5341faf289c4b988db30c1cdf8' +
'3f31251e0668b42784813801579641b2' +
'9410b3c7998d6bc465745e5c392669d6' +
'870da2c082a939e37fdcb82ec93edac9' +
'7ff3ad5950accfbc111c76f1a9529444' +
'e56aaf68c56c092cd38dc3bef5d20a93' +
'9926ed4f74a13eddfbe1a1cecc4894af' +
'9428c2b7b8883fe4463a4bc85b1cb3c1');
HexToHugeWordB(
'eecfae81b1b9b3c908810b10a1b56001' +
'99eb9f44aef4fda493b81a9e3d84f632' +
'124ef0236e5d1e3b7e28fae7aa040a2d' +
'5b252176459d1f397541ba2a58fb6599',
Pri.Prime1);
HexToHugeWordB(
'c97fb1f027f453f6341233eaaad1d935' +
'3f6c42d08866b1d05a0f2035028b9d86' +
'9840b41666b42e92ea0da3b43204b5cf' +
'ce3352524d0416a5a441e700af461503',
Pri.Prime2);
RSAPublicKeyAssignHex(Pub,
1024,
'bbf82f090682ce9c2338ac2b9da871f7' +
'368d07eed41043a440d6b6f07454f51f' +
'b8dfbaaf035c02ab61ea48ceeb6fcd48' +
'76ed520d60e1ec4619719d8a5b8b807f' +
'afb8e0a3dfc737723ee6b4b7d93a2584' +
'ee6a649d060953748834b2454598394e' +
'e0aab12d7b61a51f527a9a41f6c1687f' +
'e2537298ca2a8f5946f8e5fd091dbdcb',
'00000011');
TestStr(rsaetRSAES_OAEP,
RawByteString(
#$d4#$36#$e9#$95#$69#$fd#$32#$a7#$c8#$a0#$5b#$bc#$90#$d3#$2c#$49));
end;
procedure TestCase2;
begin
RSAPrivateKeyAssignHex(Pri,
1024,
'FED6F2848D95AFACE2354A771792D30E57B0C964D33BD700A53B92FCE0EFC7ADE2DEBC947FE762BD' +
'07F5C803ACB2CC603796E2D12684C1F827B0544575D1EE7E93500F2011A853EBBFECA78781D29D6D' +
'46B347FE76F209C0F4B9F1D457843B432CEA8060369748D858222F773758BAB16301345B02AEC17B' +
'2C09E7CE9D37F4C5',
'89DCC2AA0EE65179578EB8D020829F86FCCD78C600B838A1F2C17DCD2BEACBBD382483245AE55437' +
'2B1D3DAD2F3A32F242607027F18C945AA92DED08FEAA293860221F0838132036F833B2203EDA26C1' +
'5C90664A5627E3C6B1B4EF621FE34D239AD144F28C4891BF0354FE1D5C2FBA62B3F9A4793B835B5B' +
'6A1A3EE0AF995E69');
RSAPublicKeyAssignHex(Pub,
1024,
'FED6F2848D95AFACE2354A771792D30E57B0C964D33BD700A53B92FCE0EFC7ADE2DEBC947FE762BD' +
'07F5C803ACB2CC603796E2D12684C1F827B0544575D1EE7E93500F2011A853EBBFECA78781D29D6D' +
'46B347FE76F209C0F4B9F1D457843B432CEA8060369748D858222F773758BAB16301345B02AEC17B' +
'2C09E7CE9D37F4C5',
'00010001');
TestStr(rsaetRSAES_PKCS1, '');
TestStr(rsaetRSAES_OAEP, '');
TestStr(rsaetRSAES_PKCS1, 'Fundamentals');
TestStr(rsaetRSAES_OAEP, '12345678901234567890123456789012345678901234567890');
end;
procedure TestSign;
var Msg : RawByteString;
Hash : T256BitDigest;
Sign : RawByteString;
L : Integer;
begin
RSAPrivateKeyAssignHex(Pri,
1024,
'FED6F2848D95AFACE2354A771792D30E57B0C964D33BD700A53B92FCE0EFC7ADE2DEBC947FE762BD' +
'07F5C803ACB2CC603796E2D12684C1F827B0544575D1EE7E93500F2011A853EBBFECA78781D29D6D' +
'46B347FE76F209C0F4B9F1D457843B432CEA8060369748D858222F773758BAB16301345B02AEC17B' +
'2C09E7CE9D37F4C5',
'89DCC2AA0EE65179578EB8D020829F86FCCD78C600B838A1F2C17DCD2BEACBBD382483245AE55437' +
'2B1D3DAD2F3A32F242607027F18C945AA92DED08FEAA293860221F0838132036F833B2203EDA26C1' +
'5C90664A5627E3C6B1B4EF621FE34D239AD144F28C4891BF0354FE1D5C2FBA62B3F9A4793B835B5B' +
'6A1A3EE0AF995E69');
RSAPublicKeyAssignHex(Pub,
1024,
'FED6F2848D95AFACE2354A771792D30E57B0C964D33BD700A53B92FCE0EFC7ADE2DEBC947FE762BD' +
'07F5C803ACB2CC603796E2D12684C1F827B0544575D1EE7E93500F2011A853EBBFECA78781D29D6D' +
'46B347FE76F209C0F4B9F1D457843B432CEA8060369748D858222F773758BAB16301345B02AEC17B' +
'2C09E7CE9D37F4C5',
'00010001');
Msg := 'Test message 123';
Hash := CalcSHA256(Msg);
SetLength(Sign, 128);
L := RSASignMessage(rsastMessage, rsahfSHA256, Pri, Hash, SizeOf(Hash), Sign[1], Length(Sign));
Assert(L = 128);
Assert(RSACheckSignature(rsastMessage, Pub, Hash, SizeOf(Hash), Sign[1], L));
end;
begin
RSAPrivateKeyInit(Pri);
RSAPublicKeyInit(Pub);
TestMGF;
TestEncrypt;
TestCase1;
TestCase2;
TestSign;
RSAPublicKeyFinalise(Pub);
RSAPrivateKeyFinalise(Pri);
end;
{$ENDIF}
{$IFDEF OS_WIN}
{$IFDEF CIPHER_PROFILE}
procedure Profile;
const KeySize = 1024 + 512;
var T : Word32;
Pri, Pr2 : TRSAPrivateKey;
Pub, Pu2 : TRSAPublicKey;
Pln, Enc, Dec : RawByteString;
begin
SetRandomSeed($12345679);
RSAPrivateKeyInit(Pri);
RSAPublicKeyInit(Pub);
RSAPrivateKeyInit(Pr2);
RSAPublicKeyInit(Pu2);
T := GetTickCount;
//RSAGenerateKeys(KeySize, Pr2, Pu2);
RSAGenerateKeys(KeySize, Pri, Pub);
T := GetTickCount - T;
{ Pri.KeySize := Pu2.KeySize;
Pri.Modulus := Pu2.Modulus;
Pri.Exponent := Pu2.Exponent;
Pub.KeySize := Pr2.KeySize;
Pub.Modulus := Pr2.Modulus;
Pub.Exponent := Pr2.Exponent; }
Writeln('GenerateKeys: ', T / 1000.0:0:2, 's');
Writeln('Pri.Mod:');
Writeln(HugeWordToHexB(Pri.Modulus));
Writeln('Pri.Exp:');
Writeln(HugeWordToHexB(Pri.Exponent));
Writeln('Pub.Mod:');
Writeln(HugeWordToHexB(Pub.Modulus));
Writeln('Pub.Exp:');
Writeln(HugeWordToHexB(Pub.Exponent));
T := GetTickCount;
Pln := '123456';
Enc := RSAEncryptStr(rsaetRSAES_PKCS1, Pub, Pln);
Assert(Enc <> Pln);
T := GetTickCount - T;
Writeln('EncryptStr: ', T, 'ms');
T := GetTickCount;
Dec := RSADecryptStr(rsaetRSAES_PKCS1, Pri, Enc);
Assert(Dec = Pln);
T := GetTickCount - T;
Writeln('DecryptStr: ', T, 'ms');
RSAPublicKeyFinalise(Pub);
RSAPrivateKeyFinalise(Pri);
end;
{$ENDIF}
{$ENDIF}
end.