Permalink
Find file Copy path
60246c6 Oct 27, 2018
2 contributors

Users who have contributed to this file

@synopse @ssoftpro
15150 lines (14280 sloc) 516 KB
/// fast cryptographic routines (hashing and cypher)
// - implements AES,XOR,ADLER32,MD5,RC4,SHA1,SHA256,SHA384,SHA512,SHA3 and JWT
// - optimized for speed (tuned assembler and AES-NI / PADLOCK support)
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynCrypto;
(*
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2018 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse mORMot framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2018
the Initial Developer. All Rights Reserved.
Contributor(s):
- Alfred Glaenzer (alf)
- Eric Grange for SHA-3 MMX asm optimization
- EvaF
- Intel's sha256_sse4.asm under under a three-clause Open Software license
- Johan Bontes
- souchaud
- Project Nayuki (MIT License) for SHA-512 optimized x86 asm
- Wolfgang Ehrhardt under zlib license for SHA-3 and AES "pure pascal" code
- Maxim Masiutin for the MD5 asm
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Synopse Cryptographic routines
==============================
- fastest ever 100% Delphi (and asm ;) code
- AES Crypto(128,192,256 bits key) with optimized asm version
and multi-threaded code for multi-core CPU for blocks > 512 KB
- XOR Crypto (32 bits key) - very fast with variable or fixed key
- RC4 Crypto - weak, but simple and standard (used e.g. by SynPdf)
- ADLER32 - 32 bits fast Hash with optimized asm version
- MD5 - standard fast 128 bits Hash
- SHA-1 - 160 bits Secure Hash
- SHA-256 - 256 bits Secure Hash with optimized asm version
- SHA-512 - 512 bits Secure Hash with optimized asm version (with SHA-384)
- SHA-3 - 224/256/384/512/Shake algorithms based on Keccak permutation
- hardware AES-NI and SHA-SSE4 support for latest CPU
- VIA PADLOCK optional support - native .o code on linux or .dll (Win32)
(tested on a Dedibox C7 (rev1) linux server - need validation for Win32)
- Microsoft AES Cryptographic Provider optional support via CryptoAPI
Source code licenced under the MPL:
see http://www.mozilla.org/MPL/MPL-1.1.html
(old) Benchmark on my AMD-64 TL-56 dualcore-CPU:
================================================
Testing with blocks of 16KB each
crc32 624 MB/s
adler32 pas 571 MB/s asm 1304 MB/s
MD5 176 MB/s
SHA1 101 MB/s
SHA256 63 MB/s
AES128 cypher 84 MB/s uncypher 81 MB/s asm version
AES128 cypher 57 MB/s uncypher 57 MB/s pascal version
AES192 cypher 72 MB/s uncypher 70 MB/s asm version
AES192 cypher 48 MB/s uncypher 48 MB/s pascal version
AES256 cypher 62 MB/s uncypher 61 MB/s asm version
AES256 cypher 42 MB/s uncypher 42 MB/s pascal version
XorBlock 3463 MB/s (very fast, since with 16KB data remain in L2 cache)
XorOffset 3425 MB/s
XorConst 5940 MB/s (even faster, since no table used -> all in L1 cache)
Testing with blocks of 1024KB each (for AES: block >512KB -> uses dualcore)
crc32 577 MB/s
adler32 pas 529 MB/s asm 1003 MB/s
MD5 176 MB/s
SHA1 100 MB/s
SHA256 63 MB/s
AES128 cypher 129 MB/s uncypher 130 MB/s asm version
AES128 cypher 96 MB/s uncypher 95 MB/s pascal version
AES192 cypher 107 MB/s uncypher 114 MB/s asm version
AES192 cypher 83 MB/s uncypher 85 MB/s pascal version
AES256 cypher 98 MB/s uncypher 105 MB/s asm version
AES256 cypher 76 MB/s uncypher 76 MB/s pascal version
XorBlock 1423 MB/s (we reach the memory control bandwidth)
XorOffset 1325 MB/s
XorConst 1506 MB/s
Testing with blocks of 4096KB each (for AES: block >512KB -> uses dualcore)
crc32 578 MB/s
adler32 pas 525 MB/s asm 984 MB/s
MD5 175 MB/s
SHA1 100 MB/s
SHA256 63 MB/s
AES128 cypher 159 MB/s uncypher 147 MB/s asm version
AES128 cypher 107 MB/s uncypher 109 MB/s pascal version
AES192 cypher 134 MB/s uncypher 128 MB/s asm version
AES192 cypher 90 MB/s uncypher 92 MB/s pascal version
AES256 cypher 118 MB/s uncypher 113 MB/s asm version
AES256 cypher 80 MB/s uncypher 81 MB/s pascal version
XorBlock 1385 MB/s
XorOffset 1292 MB/s
XorConst 1479 MB/s
(old) Benchmark on a C7 Dedibox (USEPADLOCK version):
=====================================================
Testing with blocks of 16KB each
crc32 402 MB/s
adler32 pas 274 MB/s asm 542 MB/s libz.so 414 MB/s
MD5 126 MB/s
SHA1 480 MB/s
SHA256 458 MB/s
AES128 cypher 1566 MB/s uncypher 1560 MB/s
AES192 cypher 1421 MB/s uncypher 1422 MB/s
AES256 cypher 1237 MB/s uncypher 1247 MB/s
XorBlock 2336 MB/s
XorOffset 1807 MB/s
XorConst 3154 MB/s
Testing with blocks of 1024KB each
crc32 352 MB/s
adler32 pas 256 MB/s asm 395 MB/s libz.so 361 MB/s
MD5 123 MB/s
SHA1 324 MB/s
SHA256 324 MB/s
AES128 cypher 552 MB/s uncypher 552 MB/s
AES192 cypher 552 MB/s uncypher 552 MB/s
AES256 cypher 552 MB/s uncypher 552 MB/s
XorBlock 354 MB/s
XorOffset 373 MB/s
XorConst 511 MB/s
Testing with blocks of 4096KB each
crc32 352 MB/s
adler32 pas 255 MB/s asm 395 MB/s libz.so 361 MB/s
MD5 124 MB/s
SHA1 324 MB/s
SHA256 326 MB/s
AES128 cypher 552 MB/s uncypher 552 MB/s
AES192 cypher 552 MB/s uncypher 552 MB/s
AES256 cypher 552 MB/s uncypher 552 MB/s
XorBlock 352 MB/s
XorOffset 368 MB/s
XorConst 510 MB/s
Conclusion:
- USETHREADSFORBIGAESBLOCKS will help on modern multi-threaded CPU
- AES speed: W.Ehrhardt's pascal is 55MB/s, A.Bouchez's asm is 84MB/s
- AES-256 is faster than a simple XOR() on a dedibox with a C7 cpu ;)
- see below for benchmarks using AES-NI, SHA-256-SSE4, which induce
a huge performance boost
Initial version (C) 2008-2009 Arnaud Bouchez http://bouchez.info
Revision History:
Version 1.0
- initial release on Internet, with MyCrypto unit name
Version 1.1
- updated release, with new optimized AES i386 assembler implementation
and no FastCode dependency (CpuCount is taken from Windows API)
Version 1.4 - February 8, 2010
- whole Synopse SQLite3 database framework released under the GNU Lesser
General Public License version 3, instead of generic "Public Domain"
Version 1.8
- mostly code review for Delphi 2009/2010 integration (unit uses now
SynCommons string types definitions)
Version 1.9
- now use direct Windows threads, since we don't need any exception handling
nor memory usage inside the AES encryption Thread handler
-> avoid classes.TThread and system.BeginThread() use
-> application is still "officialy" mono-threaded (i.e. IsMultiThread=false),
for faster System.pas and FastMM4 (prevent CPU locking - see
https://synopse.info/forum/viewtopic.php?id=57 about Delphi & multi-core)
- some other minor fixes and enhancements
Version 1.10
- code modifications to compile with Delphi 6 compiler
Version 1.13
- code modifications to compile with Delphi 5 compiler
Version 1.15
- unit now tested with Delphi XE2 (32 Bit)
Version 1.16
- added TAESECB, TAESCBC, TAESCFB, TAESOFB and TAESCTR classes to handle AES
encryption of memory buffers in ECB, CBC, CFB, OFB and CTR mode (including
PKCS7 padding)
- added pure pascal version (for XE2 64 compilation) of all algorithms
Version 1.18
- added AES-NI hardware support on newer CPUs, for huge performance boost
and enhanced security
- AES encryption will compute its own tables, to get rid of 4KB of const
- optimized x86 and x64 asm version for MD5
- tested compilation for Win64 platform
- run with FPC under Windows and Linux (including AES-NI support), and Kylix
- added Intel's SSE4 x64 optimized asm for SHA-256 on Win64
- added overloaded procedure TMD5.Final() and function SHA-256()
- introduce ESynCrypto exception class dedicated to this unit
- added AES encryption using official Microsoft AES Cryptographic Provider
(CryptoAPI) via TAESECB_API, TAESCBC_API, TAESCFB_API and TAESOFB_API -
our optimized asm version is faster, so is still our default/preferred
- added optional IVAtBeginning parameter to EncryptPKCS7/DecryptPKC7 methods
- get rid of the unsafe IV parameter for TAES* classes constructors
- added CompressShaAes() and global CompressShaAesKey and CompressShaAesClass
variables to be used by THttpSocket.RegisterCompress
- introduce new TRC4 object for RC4 encryption algorithm
- introducing TSHA384, TSHA512 and TSHA3 objects for SHA-384, SHA-512 and
SHA-3 algorithms
- new HMAC_SHA1/SHA256/SHA384/SHA512 and PBKDF2_HMAC_SHA1/SHA256/SHA384/SHA512 functions
- removed several compilation hints when assertions are set to off
*)
interface
{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER
{.$define USEPADLOCK}
{.$define AESPASCAL} // for debug
{$ifdef Linux}
{$undef USETHREADSFORBIGAESBLOCKS} // uses low-level WinAPI threading
{$ifdef KYLIX3}
{.$define USEPADLOCK} // dedibox Linux tested only
{$endif}
{$else}
{$ifndef DELPHI5OROLDER}
// on Windows: enable Microsoft AES Cryptographic Provider (XP SP3 and up)
{$define USE_PROV_RSA_AES}
{$endif}
// on Windows: will use Threads for very big blocks (>512KB) if multi-CPU
{$define USETHREADSFORBIGAESBLOCKS}
{$endif}
{$ifdef USEPADLOCK}
{$ifdef MSWINDOWS}
{$define USEPADLOCKDLL} // Win32: we can use LibPadlock.dll
{$else}
{.$define PADLOCKDEBUG} // display message before using padlock
{.$define USEPADLOCKDLL} // Linux: use fast .o linked code
{$endif}
{$endif}
uses
{$ifdef MSWINDOWS}
Windows,
{$else}
{$ifdef KYLIX3}
LibC,
SynKylix,
{$endif}
{$ifdef FPC}
BaseUnix,
SynFPCLinux,
{$endif FPC}
{$endif MSWINDOWS}
SysUtils,
{$ifndef LVCL}
{$ifndef DELPHI5OROLDER}
RTLConsts,
{$endif}
{$endif LVCL}
Classes,
SynLZ, // already included in SynCommons, and used by CompressShaAes()
SynCommons;
{$ifdef DELPHI5OROLDER}
{$define AES_PASCAL} // Delphi 5 internal asm is buggy :(
{$define SHA3_PASCAL}
{$define SHA512_X86} // external sha512-x86.obj
{$else}
{$ifdef CPUINTEL} // AES-NI supported for x86 and x64 under Windows
{$ifdef CPU64}
{$ifdef HASAESNI}
{$define USEAESNI}
{$define USEAESNI64}
{$else}
{$define AES_PASCAL} // Delphi XE2/XE3 do not have the AES-NI opcodes :(
{$endif}
{$define AESPASCAL_OR_CPU64}
{$ifndef BSD}
{$define CRC32C_X64} // external crc32_iscsi_01 for win64/lin64
{$define SHA512_X64} // external sha512_sse4 for win64/lin64
{$endif}
{$else}
{$ifdef MSWINDOWS}
{$define SHA512_X86} // external sha512-x86.obj/.o
{$endif}
{$ifdef FPC_PIC}
{$define AES_PASCAL} // x86 AES asm below is not PIC-safe
{$else}
{$define CPUX86_NOTPIC}
{$endif FPC_PIC}
{$ifdef FPC}
{$ifdef DARWIN}
{$define AES_PASCAL} // as reported by alf
{$endif DARWIN}
{$ifdef LINUX}
{$ifndef AES_PASCAL}
{$define SHA512_X86} // external linux32/sha512-x86.o
{$endif AES_PASCAL}
{$endif}
{$endif FPC}
{$ifndef AES_PASCAL}
{$define USEAESNI} // some functions are not PIC-safe
{$define USEAESNI32}
{$endif AES_PASCAL}
{$endif}
{$else}
{$define AES_PASCAL}
{$define SHA3_PASCAL}
{$endif CPUINTEL}
{$endif}
{$ifdef AES_PASCAL}
{$define AESPASCAL_OR_CPU64}
{$endif}
{.$define AES_ROLLED}
// if defined, use rolled version, which is slightly slower (at least on my CPU)
{$ifndef AESPASCAL_OR_CPU64}
{$define AES_ROLLED} // asm requires rolled decryption keys
{$endif}
{$ifdef CPUX64}
{$define AES_ROLLED} // asm requires rolled decryption keys
{$endif}
{$ifdef USEPADLOCK}
var
/// if dll/so and VIA padlock compatible CPU are present
padlock_available: boolean = false;
{$endif}
const
/// hide all AES Context complex code
AESContextSize = 276+sizeof(pointer){$ifdef USEPADLOCK}*2{$endif}
{$ifdef USEAESNI32}+sizeof(pointer){$endif};
/// hide all SHA-1/SHA-2 complex code by storing the context as buffer
SHAContextSize = 108;
/// hide all SHA-3 complex code by storing the Keccak Sponge as buffer
SHA3ContextSize = 412;
/// power of two for a standard AES block size during cypher/uncypher
// - to be used as 1 shl AESBlockShift or 1 shr AESBlockShift for fast div/mod
AESBlockShift = 4;
/// bit mask for fast modulo of AES block size
AESBlockMod = 15;
/// maximum AES key size (in bytes)
AESKeySize = 256 div 8;
type
/// class of Exceptions raised by this unit
ESynCrypto = class(ESynException);
PAESBlock = ^TAESBlock;
/// 128 bits memory block for AES data cypher/uncypher
TAESBlock = THash128;
/// 256 bits memory block for maximum AES key storage
TAESKey = THash256;
/// stores an array of THash128 to check for their unicity
// - used e.g. to implement TAESAbstract.IVHistoryDepth property, but may be
// also used to efficiently store a list of 128-bit IPv6 addresses
{$ifdef UNICODE}THash128History = record{$else}THash128History = object{$endif}
private
Previous: array of THash128Rec;
Index: integer;
public
/// how many THash128 values can be stored
Depth: integer;
/// how many THash128 values are currently stored
Count: integer;
/// initialize the storage for a given history depth
procedure Init(size, maxsize: integer);
/// O(n) fast search of a hash value in the stored entries
// - returns true if the hash was found, or false if it did not appear
function Exists(const hash: THash128): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// add a hash value to the stored entries, checking for duplicates
// - returns true if the hash was added, or false if it did already appear
function Add(const hash: THash128): boolean;
end;
PAES = ^TAES;
/// handle AES cypher/uncypher
// - this is the default Electronic codebook (ECB) mode
// - this class will use AES-NI hardware instructions, if available
{$ifdef USEPADLOCK}
// - this class will use VIA PadLock instructions, if available
{$endif}
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance, if needed
{$ifdef UNICODE}TAES = record{$else}TAES = object{$endif}
private
Context: packed array[1..AESContextSize] of byte;
{$ifdef USEPADLOCK}
function DoPadlockInit(const Key; KeySize: cardinal): boolean;
{$endif}
public
/// Initialize AES contexts for cypher
// - first method to call before using this object for encryption
// - KeySize is in bits, i.e. 128,192,256
function EncryptInit(const Key; KeySize: cardinal): boolean;
/// encrypt an AES data block into another data block
procedure Encrypt(const BI: TAESBlock; var BO: TAESBlock); overload;
{$ifdef FPC}inline;{$endif}
/// encrypt an AES data block
procedure Encrypt(var B: TAESBlock); overload;
{$ifdef FPC}inline;{$endif}
/// Initialize AES contexts for uncypher
// - first method to call before using this object for decryption
// - KeySize is in bits, i.e. 128,192,256
function DecryptInit(const Key; KeySize: cardinal): boolean;
/// Initialize AES contexts for uncypher, from another TAES.EncryptInit
function DecryptInitFrom(const Encryption{$ifndef DELPHI5OROLDER}: TAES{$endif};
const Key; KeySize: cardinal): boolean;
/// decrypt an AES data block
procedure Decrypt(var B: TAESBlock); overload;
{$ifdef FPC}inline;{$endif}
/// decrypt an AES data block into another data block
procedure Decrypt(const BI: TAESBlock; var BO: TAESBlock); overload;
{$ifdef FPC}inline;{$endif}
/// Finalize AES contexts for both cypher and uncypher
// - would fill the TAES instance with zeros, for safety
// - is only mandatoy when padlock is used
procedure Done;
/// generic initialization method for AES contexts
// - call either EncryptInit() either DecryptInit() method
function DoInit(const Key; KeySize: cardinal; doEncrypt: boolean): boolean;
/// perform the AES cypher or uncypher to continuous memory blocks
// - call either Encrypt() either Decrypt() method
procedure DoBlocks(pIn, pOut: PAESBlock; out oIn, oOut: PAESBLock; Count: integer; doEncrypt: boolean); overload;
/// perform the AES cypher or uncypher to continuous memory blocks
// - call either Encrypt() either Decrypt() method
procedure DoBlocks(pIn, pOut: PAESBlock; Count: integer; doEncrypt: boolean); overload;
{$ifdef USETHREADSFORBIGAESBLOCKS}
/// perform the AES cypher or uncypher to continuous memory blocks
// - this special method will use Threads for bigs blocks (>512KB) if multi-CPU
// - call either Encrypt() either Decrypt() method
procedure DoBlocksThread(var bIn, bOut: PAESBlock; Count: integer; doEncrypt: boolean);
{$endif}
/// performs AES-OFB encryption and decryption on whole blocks
// - may be called instead of TAESOFB when only a raw TAES is available
// - this method is thread-safe (except if padlock is used)
procedure DoBlocksOFB(const iv: TAESBlock; src, dst: pointer; blockcount: PtrUInt);
/// TRUE if the context was initialized via EncryptInit/DecryptInit
function Initialized: boolean; {$ifdef FPC}inline;{$endif}
/// return TRUE if the AES-NI instruction sets are available on this CPU
function UsesAESNI: boolean; {$ifdef HASINLINE}inline;{$endif}
/// returns the key size in bits (128/192/256)
function KeyBits: integer; {$ifdef FPC}inline;{$endif}
end;
/// class-reference type (metaclass) of an AES cypher/uncypher
TAESAbstractClass = class of TAESAbstract;
/// used internally by TAESAbstract to detect replay attacks
// - when EncryptPKCS7/DecryptPKCS7 are used with IVAtBeginning=true, and
// IVReplayAttackCheck property contains repCheckedIfAvailable or repMandatory
// - EncryptPKCS7 will encrypt this record (using the global shared
// AESIVCTR_KEY over AES-128) to create a random IV, as a secure
// cryptographic pseudorandom number generator (CSPRNG), nonce and ctr
// ensuring 96 bits of entropy
// - DecryptPKCS7 will decode and ensure that the IV has an increasing CTR
// - memory size matches an TAESBlock on purpose, for direct encryption
TAESIVCTR = packed record
/// 8 bytes of random value
nonce: QWord;
/// contains the crc32c hash of the block cipher mode (e.g. 'AESCFB')
// - when magic won't match (i.e. in case of mORMot revision < 3063), the
// check won't be applied in DecryptPKCS7: this security feature is
// backward compatible if IVReplayAttackCheck is repCheckedIfAvailable,
// but will fail for repMandatory
magic: cardinal;
/// an increasing counter, used to detect replay attacks
// - is set to a 32-bit random value at initialization
// - is increased by one for every EncryptPKCS7, so can be checked against
// replay attack in DecryptPKCS7, and implement a safe CSPRNG for stored IV
ctr: cardinal;
end;
/// how TAESAbstract.DecryptPKCS7 should detect replay attack
// - repNoCheck and repCheckedIfAvailable will be compatible with older
// versions of the protocol, but repMandatory will reject any encryption
// without the TAESIVCTR algorithm
TAESIVReplayAttackCheck = (repNoCheck, repCheckedIfAvailable, repMandatory);
/// handle AES cypher/uncypher with chaining
// - use any of the inherited implementation, corresponding to the chaining
// mode required - TAESECB, TAESCBC, TAESCFB, TAESOFB and TAESCTR classes to
// handle in ECB, CBC, CFB, OFB and CTR mode (including PKCS7-like padding)
TAESAbstract = class(TSynPersistent)
protected
fKeySize: cardinal;
fKeySizeBytes: cardinal;
fKey: TAESKey;
fIV: TAESBlock;
fIVCTR: TAESIVCTR;
fIVCTRState: (ctrUnknown, ctrUsed, ctrNotused);
fIVHistoryDec: THash128History;
fIVReplayAttackCheck: TAESIVReplayAttackCheck;
procedure SetIVHistory(aDepth: integer);
procedure SetIVCTR;
function DecryptPKCS7Len(var InputLen,ivsize: integer; Input: pointer;
IVAtBeginning, RaiseESynCryptoOnError: boolean): boolean;
public
/// Initialize AES context for cypher
// - first method to call before using this class
// - KeySize is in bits, i.e. 128,192,256
constructor Create(const aKey; aKeySize: cardinal); reintroduce; overload; virtual;
/// Initialize AES context for AES-128 cypher
// - first method to call before using this class
// - just a wrapper around Create(aKey,128);
constructor Create(const aKey: THash128); reintroduce; overload;
/// Initialize AES context for AES-256 cypher
// - first method to call before using this class
// - just a wrapper around Create(aKey,256);
constructor Create(const aKey: THash256); reintroduce; overload;
/// Initialize AES context for cypher, from some TAESPRNG random bytes
// - may be used to hide some sensitive information from memory, like
// CryptDataForCurrentUser but with a temporary key
constructor CreateTemp(aKeySize: cardinal);
/// Initialize AES context for cypher, from SHA-256 hash
// - here the Key is supplied as a string, and will be hashed using SHA-256
// via the SHA256Weak proprietary algorithm - to be used only for backward
// compatibility of existing code
// - consider using more secure (and more standard) CreateFromPBKDF2 instead
constructor CreateFromSha256(const aKey: RawUTF8);
/// Initialize AES context for cypher, from PBKDF2_HMAC_SHA256 derivation
// - here the Key is supplied as a string, and will be hashed using
// PBKDF2_HMAC_SHA256 with the specified salt and rounds
constructor CreateFromPBKDF2(const aKey: RawUTF8; const aSalt: RawByteString;
aRounds: Integer);
/// compute a class instance similar to this one
// - could be used to have a thread-safe re-use of a given encryption key
function Clone: TAESAbstract; virtual;
/// compute a class instance similar to this one, for performing the
// reverse encryption/decryption process
// - this default implementation calls Clone, but CFB/OFB/CTR chaining modes
// using only AES encryption (i.e. inheriting from TAESAbstractEncryptOnly)
// will return self to avoid creating two instances
// - warning: to be used only with IVAtBeginning=false
function CloneEncryptDecrypt: TAESAbstract; virtual;
/// release the used instance memory and resources
// - also fill the secret fKey buffer with zeros, for safety
destructor Destroy; override;
/// perform the AES cypher in the corresponding mode
// - when used in block chaining mode, you should have set the IV property
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); virtual; abstract;
/// perform the AES un-cypher in the corresponding mode
// - when used in block chaining mode, you should have set the IV property
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); virtual; abstract;
/// encrypt a memory buffer using a PKCS7 padding pattern
// - PKCS7 padding is described in RFC 5652 - it will add up to 16 bytes to
// the input buffer; note this method uses the padding only, not the whole
// PKCS#7 Cryptographic Message Syntax
// - if IVAtBeginning is TRUE, a random Initialization Vector will be computed,
// and stored at the beginning of the output binary buffer - this IV may
// contain an internal encrypted CTR, to detect any replay attack attempt,
// if IVReplayAttackCheck is set to repCheckedIfAvailable or repMandatory
function EncryptPKCS7(const Input: RawByteString; IVAtBeginning: boolean=false): RawByteString; overload;
/// decrypt a memory buffer using a PKCS7 padding pattern
// - PKCS7 padding is described in RFC 5652 - it will trim up to 16 bytes from
// the input buffer; note this method uses the padding only, not the whole
// PKCS#7 Cryptographic Message Syntax
// - if IVAtBeginning is TRUE, the Initialization Vector will be taken
// from the beginning of the input binary buffer - if IVReplayAttackCheck is
// set, this IV will be validated to contain an increasing encrypted CTR,
// and raise an ESynCrypto when a replay attack attempt is detected
// - if RaiseESynCryptoOnError=false, returns '' on any decryption error
function DecryptPKCS7(const Input: RawByteString; IVAtBeginning: boolean=false;
RaiseESynCryptoOnError: boolean=true): RawByteString; overload;
/// encrypt a memory buffer using a PKCS7 padding pattern
// - PKCS7 padding is described in RFC 5652 - it will add up to 16 bytes to
// the input buffer; note this method uses the padding only, not the whole
// PKCS#7 Cryptographic Message Syntax
// - if IVAtBeginning is TRUE, a random Initialization Vector will be computed,
// and stored at the beginning of the output binary buffer - this IV may
// contain an internal encrypted CTR, to detect any replay attack attempt,
// if IVReplayAttackCheck is set to repCheckedIfAvailable or repMandatory
function EncryptPKCS7(const Input: TBytes; IVAtBeginning: boolean=false): TBytes; overload;
/// decrypt a memory buffer using a PKCS7 padding pattern
// - PKCS7 padding is described in RFC 5652 - it will trim up to 16 bytes from
// the input buffer; note this method uses the padding only, not the whole
// PKCS#7 Cryptographic Message Syntax
// - if IVAtBeginning is TRUE, the Initialization Vector will be taken
// from the beginning of the input binary buffer - if IVReplayAttackCheck is
// set, this IV will be validated to contain an increasing encrypted CTR,
// and raise an ESynCrypto when a replay attack attempt is detected
// - if RaiseESynCryptoOnError=false, returns [] on any decryption error
function DecryptPKCS7(const Input: TBytes; IVAtBeginning: boolean=false;
RaiseESynCryptoOnError: boolean=true): TBytes; overload;
/// compute how many bytes would be needed in the output buffer, when
// encrypte using a PKCS7 padding pattern
// - could be used to pre-compute the OutputLength for EncryptPKCS7Buffer()
// - PKCS7 padding is described in RFC 5652 - it will add up to 16 bytes to
// the input buffer; note this method uses the padding only, not the whole
// PKCS#7 Cryptographic Message Syntax
function EncryptPKCS7Length(InputLen: cardinal; IVAtBeginning: boolean): cardinal;
{$ifdef HASINLINE}inline;{$endif}
/// encrypt a memory buffer using a PKCS7 padding pattern
// - PKCS7 padding is described in RFC 5652 - it will add up to 16 bytes to
// the input buffer; note this method uses the padding only, not the whole
// PKCS#7 Cryptographic Message Syntax
// - use EncryptPKCS7Length() function to compute the actual needed length
// - if IVAtBeginning is TRUE, a random Initialization Vector will be computed,
// and stored at the beginning of the output binary buffer - this IV will in
// fact contain an internal encrypted CTR, to detect any replay attack attempt
// - returns TRUE on success, FALSE if OutputLen is not correct - you should
// use EncryptPKCS7Length() to compute the exact needed number of bytes
function EncryptPKCS7Buffer(Input,Output: Pointer; InputLen,OutputLen: cardinal;
IVAtBeginning: boolean): boolean;
/// decrypt a memory buffer using a PKCS7 padding pattern
// - PKCS7 padding is described in RFC 5652 - it will trim up to 16 bytes from
// the input buffer; note this method uses the padding only, not the whole
// PKCS#7 Cryptographic Message Syntax
// - if IVAtBeginning is TRUE, the Initialization Vector will be taken
// from the beginning of the input binary buffer - this IV will in fact
// contain an internal encrypted CTR, to detect any replay attack attempt
// - if RaiseESynCryptoOnError=false, returns '' on any decryption error
function DecryptPKCS7Buffer(Input: Pointer; InputLen: integer;
IVAtBeginning: boolean; RaiseESynCryptoOnError: boolean=true): RawByteString;
/// initialize AEAD (authenticated-encryption with associated-data) nonce
// - i.e. setup 256-bit MAC computation during next Encrypt/Decrypt call
// - may be used e.g. for AES-GCM or our custom AES-CTR modes
// - default implementation, for a non AEAD protocol, returns false
function MACSetNonce(const aKey: THash256; aAssociated: pointer=nil;
aAssociatedLen: integer=0): boolean; virtual;
/// returns AEAD (authenticated-encryption with associated-data) MAC
/// - i.e. optional 256-bit MAC computation during last Encrypt/Decrypt call
// - may be used e.g. for AES-GCM or our custom AES-CTR modes
// - default implementation, for a non AEAD protocol, returns false
function MACGetLast(out aCRC: THash256): boolean; virtual;
/// validate if the computed AEAD MAC matches the expected supplied value
// - is just a wrapper around MACGetLast() and IsEqual() functions
function MACEquals(const aCRC: THash256): boolean; virtual;
/// validate if an encrypted buffer matches the stored AEAD MAC
// - expects the 256-bit MAC, as returned by MACGetLast, to be stored after
// the encrypted data
// - default implementation, for a non AEAD protocol, returns false
function MACCheckError(aEncrypted: pointer; Count: cardinal): boolean; virtual;
/// perform one step PKCS7 encryption/decryption and authentication from
// a given 256-bit key
// - returns '' on any (MAC) issue during decryption (Encrypt=false) or if
// this class does not support AEAD MAC
// - as used e.g. by CryptDataForCurrentUser()
// - do not use this abstract class method, but inherited TAESCFBCRC/TAESOFBCRC
// - will store a header with its own CRC, so detection of most invalid
// formats (e.g. from fuzzing input) will occur before any AES/MAC process
class function MACEncrypt(const Data: RawByteString; const Key: THash256;
Encrypt: boolean): RawByteString; overload;
/// perform one step PKCS7 encryption/decryption and authentication from
// a given 128-bit key
// - returns '' on any (MAC) issue during decryption (Encrypt=false) or if
// this class does not support AEAD MAC
// - do not use this abstract class method, but inherited TAESCFBCRC/TAESOFBCRC
// - will store a header with its own CRC, so detection of most invalid
// formats (e.g. from fuzzing input) will occur before any AES/MAC process
class function MACEncrypt(const Data: RawByteString; const Key: THash128;
Encrypt: boolean): RawByteString; overload;
/// perform one step PKCS7 encryption/decryption and authentication with
// the curent AES instance
// - returns '' on any (MAC) issue during decryption (Encrypt=false) or if
// this class does not support AEAD MAC
// - as used e.g. by CryptDataForCurrentUser()
// - do not use this abstract class method, but inherited TAESCFBCRC/TAESOFBCRC
// - will store a header with its own CRC, so detection of most invalid
// formats (e.g. from fuzzing input) will occur before any AES/MAC process
function MACAndCrypt(const Data: RawByteString; Encrypt: boolean): RawByteString;
/// simple wrapper able to cypher/decypher any in-memory content
// - here data variables could be text or binary
// - use StringToUTF8() to define the Key parameter from a VCL string
// - if IVAtBeginning is TRUE, a random Initialization Vector will be computed,
// and stored at the beginning of the output binary buffer
// - will use SHA256Weak() and PKCS7 padding with the current class mode
class function SimpleEncrypt(const Input,Key: RawByteString; Encrypt: boolean;
IVAtBeginning: boolean=false; RaiseESynCryptoOnError: boolean=true): RawByteString; overload;
/// simple wrapper able to cypher/decypher any in-memory content
// - here data variables could be text or binary
// - you could use e.g. THMAC_SHA256 to safely compute the Key/KeySize value
// - if IVAtBeginning is TRUE, a random Initialization Vector will be computed,
// and stored at the beginning of the output binary buffer
// - will use SHA256Weak() and PKCS7 padding with the current class mode
class function SimpleEncrypt(const Input: RawByteString; const Key;
KeySize: integer; Encrypt: boolean; IVAtBeginning: boolean=false;
RaiseESynCryptoOnError: boolean=true): RawByteString; overload;
/// simple wrapper able to cypher/decypher any file content
// - just a wrapper around SimpleEncrypt() and StringFromFile/FileFromString
// - use StringToUTF8() to define the Key parameter from a VCL string
// - if IVAtBeginning is TRUE, a random Initialization Vector will be computed,
// and stored at the beginning of the output binary buffer
// - will use SHA256Weak() and PKCS7 padding with the current class mode
class function SimpleEncryptFile(const InputFile, OutputFile: TFileName;
const Key: RawByteString; Encrypt: boolean; IVAtBeginning: boolean=false;
RaiseESynCryptoOnError: boolean=true): boolean; overload;
/// simple wrapper able to cypher/decypher any file content
// - just a wrapper around SimpleEncrypt() and StringFromFile/FileFromString
// - you could use e.g. THMAC_SHA256 to safely compute the Key/KeySize value
// - if IVAtBeginning is TRUE, a random Initialization Vector will be computed,
// and stored at the beginning of the output binary buffer
// - will use SHA256Weak() and PKCS7 padding with the current class mode
class function SimpleEncryptFile(const InputFile, Outputfile: TFileName; const Key;
KeySize: integer; Encrypt: boolean; IVAtBeginning: boolean=false;
RaiseESynCryptoOnError: boolean=true): boolean; overload;
//// returns e.g. 'aes128cfb' or '' if nil
function AlgoName: TShort16;
/// associated Key Size, in bits (i.e. 128,192,256)
property KeySize: cardinal read fKeySize;
/// associated Initialization Vector
// - all modes (except ECB) do expect an IV to be supplied for chaining,
// before any encryption or decryption is performed
// - you could also use PKCS7 encoding with IVAtBeginning=true option
property IV: TAESBlock read fIV write fIV;
/// let IV detect replay attack for EncryptPKCS7 and DecryptPKCS7
// - if IVAtBeginning=true and this property is set, EncryptPKCS7 will
// store a random IV from an internal CTR, and DecryptPKCS7 will check this
// incoming IV CTR consistency, and raise an ESynCrypto exception on failure
// - leave it to its default repNoCheck if the very same TAESAbstract
// instance is expected to be used with several sources, by which the IV CTR
// will be unsynchronized
// - security warning: by design, this is NOT cautious with CBC chaining:
// you should use it only with CFB, OFB or CTR mode, since the IV sequence
// will be predictable if you know the fixed AES private key of this unit,
// but the IV sequence features uniqueness as it is generated by a good PRNG -
// see http://crypto.stackexchange.com/q/3515
property IVReplayAttackCheck: TAESIVReplayAttackCheck
read fIVReplayAttackCheck write fIVReplayAttackCheck;
/// maintains an history of previous IV, to avoid re-play attacks
// - only useful when EncryptPKCS7/DecryptPKCS7 are used with
// IVAtBeginning=true, and IVReplayAttackCheck is left to repNoCheck
property IVHistoryDepth: integer read fIVHistoryDec.Depth write SetIVHistory;
end;
/// handle AES cypher/uncypher with chaining
// - use any of the inherited implementation, corresponding to the chaining
// mode required - TAESECB, TAESCBC, TAESCFB, TAESOFB and TAESCTR classes to
// handle in ECB, CBC, CFB, OFB and CTR mode (including PKCS7-like padding)
// - this class will use AES-NI hardware instructions, if available
// - those classes are re-entrant, i.e. that you can call the Encrypt*
// or Decrypt* methods on the same instance several times
TAESAbstractSyn = class(TAESAbstract)
protected
fIn, fOut: PAESBlock;
fCV: TAESBlock;
AES: TAES;
fCount: Cardinal;
fAESInit: (initNone, initEncrypt, initDecrypt);
procedure EncryptInit;
procedure DecryptInit;
procedure TrailerBytes;
public
/// creates a new instance with the very same values
// - by design, our classes will use stateless context, so this method
// will just copy the current fields to a new instance, by-passing
// the key creation
function Clone: TAESAbstract; override;
/// release the used instance memory and resources
// - also fill the TAES instance with zeros, for safety
destructor Destroy; override;
/// perform the AES cypher in the corresponding mode
// - this abstract method will set CV from fIV property, and fIn/fOut
// from BufIn/BufOut
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// perform the AES un-cypher in the corresponding mode
// - this abstract method will set CV from fIV property, and fIn/fOut
// from BufIn/BufOut
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// read-only access to the internal CV block, which may be have just been
// used by Encrypt/Decrypt methods
property CV: TAESBlock read fCV;
end;
/// handle AES cypher/uncypher without chaining (ECB)
// - this mode is known to be less secure than the others
// - IV property should be set to a fixed value to encode the trailing bytes
// of the buffer by a simple XOR - but you should better use the PKC7 pattern
// - this class will use AES-NI hardware instructions, if available, e.g.
// ! ECB128: 19.70ms in x86 optimized code, 6.97ms with AES-NI
TAESECB = class(TAESAbstractSyn)
public
/// perform the AES cypher in the ECB mode
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// perform the AES un-cypher in the ECB mode
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override;
end;
/// handle AES cypher/uncypher with Cipher-block chaining (CBC)
// - this class will use AES-NI hardware instructions, if available, e.g.
// ! CBC192: 24.91ms in x86 optimized code, 9.75ms with AES-NI
// - expect IV to be set before process, or IVAtBeginning=true
TAESCBC = class(TAESAbstractSyn)
public
/// perform the AES cypher in the CBC mode
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// perform the AES un-cypher in the CBC mode
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override;
end;
/// abstract parent class for chaining modes using only AES encryption
TAESAbstractEncryptOnly = class(TAESAbstractSyn)
public
/// Initialize AES context for cypher
// - will pre-generate the encryption key (aKeySize in bits, i.e. 128,192,256)
constructor Create(const aKey; aKeySize: cardinal); override;
/// compute a class instance similar to this one, for performing the
// reverse encryption/decryption process
// - will return self to avoid creating two instances
// - warning: to be used only with IVAtBeginning=false
function CloneEncryptDecrypt: TAESAbstract; override;
end;
/// handle AES cypher/uncypher with Cipher feedback (CFB)
// - this class will use AES-NI hardware instructions, if available, e.g.
// ! CFB128: 22.25ms in x86 optimized code, 9.29ms with AES-NI
// - expect IV to be set before process, or IVAtBeginning=true
TAESCFB = class(TAESAbstractEncryptOnly)
public
/// perform the AES cypher in the CFB mode
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// perform the AES un-cypher in the CFB mode
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override;
end;
/// handle AES cypher/uncypher with Output feedback (OFB)
// - this class will use AES-NI hardware instructions, if available, e.g.
// ! OFB256: 27.69ms in x86 optimized code, 9.94ms with AES-NI
// - expect IV to be set before process, or IVAtBeginning=true
// - TAESOFB 128/256 have an optimized asm version under x86_64 + AES_NI
TAESOFB = class(TAESAbstractEncryptOnly)
public
/// perform the AES cypher in the OFB mode
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// perform the AES un-cypher in the OFB mode
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override;
end;
/// handle AES cypher/uncypher with Counter mode (CTR)
// - this class will use AES-NI hardware instructions, e.g.
// ! CTR256: 28.13ms in x86 optimized code, 10.63ms with AES-NI
// - expect IV to be set before process, or IVAtBeginning=true
TAESCTR = class(TAESAbstractEncryptOnly)
public
/// perform the AES cypher in the CTR mode
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// perform the AES un-cypher in the CTR mode
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override;
end;
/// internal 256-bit structure used for TAESAbstractAEAD MAC storage
TAESMAC256 = record
/// the AES-encrypted MAC of the plain content
// - plain text digital signature, to perform message authentication
// and integrity
plain: THash128;
/// the plain MAC of the encrypted content
// - encrypted text digital signature, to check for errors,
// with no compromission of the plain content
encrypted: THash128;
end;
/// AEAD (authenticated-encryption with associated-data) abstract class
// - perform AES encryption and on-the-fly MAC computation, i.e. computes
// a proprietary 256-bit MAC during AES cyphering, as 128-bit CRC of the
// encrypted data and 128-bit CRC of the plain data, seeded from a Key
// - the 128-bit CRC of the plain text is then encrypted using the current AES
// engine, so returned 256-bit MAC value has cryptographic level, and ensure
// data integrity, authenticity, and check against transmission errors
TAESAbstractAEAD = class(TAESAbstractEncryptOnly)
protected
fMAC, fMACKey: TAESMAC256;
public
/// release the used instance memory and resources
// - also fill the internal internal MAC hashes with zeros, for safety
destructor Destroy; override;
/// initialize 256-bit MAC computation for next Encrypt/Decrypt call
// - initialize the internal fMACKey property, and returns true
// - only the plain text crc is seeded from aKey - encrypted message crc
// will use -1 as fixed seed, to avoid aKey compromission
// - should be set with a new MAC key value before each message, to avoid
// replay attacks (as called from TECDHEProtocol.SetKey)
function MACSetNonce(const aKey: THash256; aAssociated: pointer=nil;
aAssociatedLen: integer=0): boolean; override;
/// returns 256-bit MAC computed during last Encrypt/Decrypt call
// - encrypt the internal fMAC property value using the current AES cypher
// on the plain content and returns true; only the plain content CRC-128 is
// AES encrypted, to avoid reverse attacks against the known encrypted data
function MACGetLast(out aCRC: THash256): boolean; override;
/// validate if an encrypted buffer matches the stored MAC
// - expects the 256-bit MAC, as returned by MACGetLast, to be stored after
// the encrypted data
// - returns true if the 128-bit CRC of the encrypted text matches the
// supplied buffer, ignoring the 128-bit CRC of the plain data
// - since it is easy to forge such 128-bit CRC, it will only indicate
// that no transmission error occured, but won't be an integrity or
// authentication proof (which will need full Decrypt + MACGetLast)
// - may use any MACSetNonce() aAssociated value
function MACCheckError(aEncrypted: pointer; Count: cardinal): boolean; override;
end;
/// AEAD combination of AES with Cipher feedback (CFB) and 256-bit MAC
// - this class will use AES-NI and CRC32C hardware instructions, if available
// - expect IV to be set before process, or IVAtBeginning=true
TAESCFBCRC = class(TAESAbstractAEAD)
public
/// perform the AES cypher in the CFB mode, and compute a 256-bit MAC
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// perform the AES un-cypher in the CFB mode, and compute 256-bit MAC
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override;
end;
/// AEAD combination of AES with Output feedback (OFB) and 256-bit MAC
// - this class will use AES-NI and CRC32C hardware instructions, if available
// - expect IV to be set before process, or IVAtBeginning=true
TAESOFBCRC = class(TAESAbstractAEAD)
public
/// perform the AES cypher in the OFB mode, and compute a 256-bit MAC
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// perform the AES un-cypher in the OFB mode, and compute a 256-bit MAC
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override;
end;
{$ifdef USE_PROV_RSA_AES}
type
/// handle AES cypher/uncypher using Windows CryptoAPI and the
// official Microsoft AES Cryptographic Provider (PROV_RSA_AES)
// - see @http://msdn.microsoft.com/en-us/library/windows/desktop/aa386979
// - timing of our optimized asm versions, for small (<=8KB) block processing
// (similar to standard web pages or most typical JSON/XML content),
// benchmarked on a Core i7 notebook and compiled as Win32 platform:
// ! AES128 - ECB:79.33ms CBC:83.37ms CFB:80.75ms OFB:78.98ms CTR:80.45ms
// ! AES192 - ECB:91.16ms CBC:96.06ms CFB:96.45ms OFB:92.12ms CTR:93.38ms
// ! AES256 - ECB:103.22ms CBC:119.14ms CFB:111.59ms OFB:107.00ms CTR:110.13ms
// - timing of the same process, using CryptoAPI official PROV_RSA_AES provider:
// ! AES128 - ECB_API:102.88ms CBC_API:124.91ms
// ! AES192 - ECB_API:115.75ms CBC_API:129.95ms
// ! AES256 - ECB_API:139.50ms CBC_API:154.02ms
// - but the CryptoAPI does not supports AES-NI, whereas our classes handle it,
// with a huge speed benefit
// - under Win64, the official CryptoAPI is faster than our PUREPASCAL version,
// and the Win32 version of CryptoAPI itself, but slower than our AES-NI code
// ! AES128 - ECB:107.95ms CBC:112.65ms CFB:109.62ms OFB:107.23ms CTR:109.42ms
// ! AES192 - ECB:130.30ms CBC:133.04ms CFB:128.78ms OFB:127.25ms CTR:130.22ms
// ! AES256 - ECB:145.33ms CBC:147.01ms CFB:148.36ms OFB:145.96ms CTR:149.67ms
// ! AES128 - ECB_API:89.64ms CBC_API:100.84ms
// ! AES192 - ECB_API:99.05ms CBC_API:105.85ms
// ! AES256 - ECB_API:107.11ms CBC_API:118.04ms
// - in practice, you could forget about using the CryptoAPI, unless you are
// required to do so, for legal/corporate reasons
TAESAbstract_API = class(TAESAbstract)
protected
fKeyHeader: packed record
bType: byte;
bVersion: byte;
reserved: word;
aiKeyAlg: cardinal;
dwKeyLength: cardinal;
end;
fKeyHeaderKey: TAESKey; // should be just after fKeyHeader record
fKeyCryptoAPI: pointer;
fInternalMode: cardinal;
procedure InternalSetMode; virtual; abstract;
procedure EncryptDecrypt(BufIn, BufOut: pointer; Count: cardinal; DoEncrypt: boolean);
public
/// Initialize AES context for cypher
// - first method to call before using this class
// - KeySize is in bits, i.e. 128,192,256
constructor Create(const aKey; aKeySize: cardinal); override;
/// release the AES execution context
destructor Destroy; override;
/// perform the AES cypher in the ECB mode
// - if Count is not a multiple of a 16 bytes block, the IV will be used
// to XOR the trailing bytes - so it won't be compatible with our
// TAESAbstractSyn classes: you should better use PKC7 padding instead
procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override;
/// perform the AES un-cypher in the ECB mode
// - if Count is not a multiple of a 16 bytes block, the IV will be used
// to XOR the trailing bytes - so it won't be compatible with our
// TAESAbstractSyn classes: you should better use PKC7 padding instead
procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override;
end;
/// handle AES cypher/uncypher without chaining (ECB) using Windows CryptoAPI
TAESECB_API = class(TAESAbstract_API)
protected
/// will set fInternalMode := CRYPT_MODE_ECB
procedure InternalSetMode; override;
end;
/// handle AES cypher/uncypher Cipher-block chaining (CBC) using Windows CryptoAPI
TAESCBC_API = class(TAESAbstract_API)
protected
/// will set fInternalMode := CRYPT_MODE_CBC
procedure InternalSetMode; override;
end;
/// handle AES cypher/uncypher Cipher feedback (CFB) using Windows CryptoAPI
// - NOT TO BE USED: the current PROV_RSA_AES provider does not return
// expected values for CFB
TAESCFB_API = class(TAESAbstract_API)
protected
/// will set fInternalMode := CRYPT_MODE_CFB
procedure InternalSetMode; override;
end;
/// handle AES cypher/uncypher Output feedback (OFB) using Windows CryptoAPI
// - NOT TO BE USED: the current PROV_RSA_AES provider does not implement
// this mode, and returns a NTE_BAD_ALGID error
TAESOFB_API = class(TAESAbstract_API)
protected
/// will set fInternalMode := CRYPT_MODE_OFB
procedure InternalSetMode; override;
end;
{$endif USE_PROV_RSA_AES}
var
/// 128-bit random AES-128 entropy key for TAESAbstract.IVReplayAttackCheck
// - as used internally by AESIVCtrEncryptDecrypt() function
// - you may customize this secret for your own project, but be aware that
// it will affect all TAESAbstract instances, so should match on all ends
AESIVCTR_KEY: TBlock128 = (
$ce5d5e3e, $26506c65, $568e0092, $12cce480);
/// global shared function which may encrypt or decrypt any 128-bit block
// using AES-128 and the global AESIVCTR_KEY
procedure AESIVCtrEncryptDecrypt(const BI; var BO; DoEncrypt: boolean);
type
/// thread-safe class containing a TAES encryption/decryption engine
TAESLocked = class(TSynPersistent)
protected
fAES: TAES;
fLock: TRTLCriticalSection;
public
/// initialize the internal lock, but not the TAES instance
constructor Create; override;
/// finalize all used memory and resources
destructor Destroy; override;
end;
/// cryptographic pseudorandom number generator (CSPRNG) based on AES-256
// - use as a shared instance via TAESPRNG.Fill() overloaded class methods
// - this class is able to generate some random output by encrypting successive
// values of a counter with AES-256 and a secret key
// - this internal secret key is generated from PBKDF2 derivation of OS-supplied
// entropy using HMAC over SHA-512
// - by design, such a PRNG is as good as the cypher used - for reference, see
// https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator
// - it would use fast hardware AES-NI or Padlock opcodes, if available
TAESPRNG = class(TAESLocked)
protected
fCTR: THash128Rec; // we use a litle-endian CTR
fBytesSinceSeed: integer;
fSeedAfterBytes: integer;
fAESKeySize: integer;
fSeedPBKDF2Rounds: cardinal;
fTotalBytes: QWord;
procedure IncrementCTR; {$ifdef HASINLINE}inline;{$endif}
public
/// initialize the internal secret key, using Operating System entropy
// - entropy is gathered from the OS, using GetEntropy() method
// - you can specify how many PBKDF2_HMAC_SHA512 rounds are applied to the
// OS-gathered entropy - the higher, the better, but also the slower
// - internal private key would be re-seeded after ReseedAfterBytes
// bytes (1MB by default) are generated, using GetEntropy()
// - by default, AES-256 will be used, unless AESKeySize is set to 128,
// which may be slightly faster (especially if AES-NI is not available)
constructor Create(PBKDF2Rounds: integer = 16;
ReseedAfterBytes: integer = 1024*1024; AESKeySize: integer = 256); reintroduce; virtual;
/// fill a TAESBlock with some pseudorandom data
// - could be used e.g. to compute an AES Initialization Vector (IV)
// - this method is thread-safe
procedure FillRandom(out Block: TAESBlock); overload; virtual;
/// fill a 256-bit buffer with some pseudorandom data
// - this method is thread-safe
procedure FillRandom(out Buffer: THash256); overload;
/// fill a binary buffer with some pseudorandom data
// - this method is thread-safe
procedure FillRandom(Buffer: pointer; Len: integer); overload; virtual;
/// returns a binary buffer filled with some pseudorandom data
// - this method is thread-safe
function FillRandom(Len: integer): RawByteString; overload;
/// returns a binary buffer filled with some pseudorandom data
// - this method is thread-safe
function FillRandomBytes(Len: integer): TBytes;
/// returns an hexa-encoded binary buffer filled with some pseudorandom data
// - this method is thread-safe
function FillRandomHex(Len: integer): RawUTF8;
/// returns a 32-bit unsigned random number
function Random32: cardinal; overload;
/// returns a 32-bit unsigned random number, with a maximum value
function Random32(max: cardinal): cardinal; overload;
/// returns a 64-bit unsigned random number
function Random64: QWord;
/// returns a floating-point random number in range [0..1]
function RandomExt: TSynExtended;
/// computes a random ASCII password
// - will contain uppercase/lower letters, digits and $.:()?%!-+*/@#
// excluding ;,= to allow direct use in CSV content
function RandomPassword(Len: integer): RawUTF8;
/// would force the internal generator to re-seed its private key
// - avoid potential attacks on backward or forward security
// - would be called by FillRandom() methods, according to SeedAfterBytes
// - this method is thread-safe
procedure Seed; virtual;
/// retrieve some entropy bytes from the Operating System
// - entropy comes from CryptGenRandom API on Windows, and /dev/urandom or
// /dev/random on Linux/POSIX
// - this system-supplied entropy is then XORed with the output of a SHA-3
// cryptographic SHAKE-256 generator in XOF mode, of several entropy sources
// (timestamp, thread and system information, SynCommons.Random32 function)
// unless SystemOnly is TRUE
// - depending on the system, entropy may not be true randomness: if you need
// some truly random values, use TAESPRNG.Main.FillRandom() or TAESPRNG.Fill()
// methods, NOT this class function (which will be much slower, BTW)
class function GetEntropy(Len: integer; SystemOnly: boolean=false): RawByteString; virtual;
/// returns a shared instance of a TAESPRNG instance
// - if you need to generate some random content, just call the
// TAESPRNG.Main.FillRandom() overloaded methods, or directly TAESPRNG.Fill()
class function Main: TAESPRNG;
{$ifdef HASINLINE}inline;{$endif}
/// just a wrapper around TAESPRNG.Main.FillRandom() function
// - this method is thread-safe, but you may use your own TAESPRNG instance
// if you need some custom entropy level
class procedure Fill(Buffer: pointer; Len: integer); overload;
{$ifdef HASINLINE}inline;{$endif}
/// just a wrapper around TAESPRNG.Main.FillRandom() function
// - this method is thread-safe, but you may use your own TAESPRNG instance
// if you need some custom entropy level
class procedure Fill(out Block: TAESBlock); overload;
/// just a wrapper around TAESPRNG.Main.FillRandom() function
// - this method is thread-safe, but you may use your own TAESPRNG instance
// if you need some custom entropy level
class procedure Fill(out Block: THash256); overload;
{$ifdef HASINLINE}inline;{$endif}
/// just a wrapper around TAESPRNG.Main.FillRandom() function
// - this method is thread-safe, but you may use your own TAESPRNG instance
// if you need some custom entropy level
class function Fill(Len: integer): RawByteString; overload;
{$ifdef HASINLINE}inline;{$endif}
/// just a wrapper around TAESPRNG.Main.FillRandomBytes() function
// - this method is thread-safe, but you may use your own TAESPRNG instance
// if you need some custom entropy level
class function Bytes(Len: integer): TBytes;
{$ifdef HASINLINE}inline;{$endif}
/// create an anti-forensic representation of a key for safe storage
// - a binary buffer will be split into StripesCount items, ready to be
// saved on disk; returned length is BufferBytes*(StripesCount+1) bytes
// - AFSplit supports secure data destruction crucial for secure on-disk
// key management. The key idea is to bloat information and therefore
// improve the chance of destroying a single bit of it. The information
// is bloated in such a way, that a single missing bit causes the original
// information become unrecoverable.
// - this implementation uses SHA-256 as diffusion element, and the current
// TAESPRNG instance to gather randomness
// - for reference, see TKS1 as used for LUKS and defined in
// @https://gitlab.com/cryptsetup/cryptsetup/wikis/TKS1-draft.pdf
function AFSplit(const Buffer; BufferBytes, StripesCount: integer): RawByteString; overload;
/// create an anti-forensic representation of a key for safe storage
// - a binary buffer will be split into StripesCount items, ready to be
// saved on disk; returned length is BufferBytes*(StripesCount+1) bytes
// - jsut a wrapper around the other overloaded AFSplit() funtion
function AFSplit(const Buffer: RawByteString; StripesCount: integer): RawByteString; overload;
/// retrieve a key from its anti-forensic representation
// - is the reverse function of AFSplit() method
// - returns TRUE if the input buffer matches BufferBytes value
class function AFUnsplit(const Split: RawByteString;
out Buffer; BufferBytes: integer): boolean; overload;
/// retrieve a key from its anti-forensic representation
// - is the reverse function of AFSplit() method
// - returns the un-splitted binary content
// - returns '' if StripesCount is incorrect
class function AFUnsplit(const Split: RawByteString;
StripesCount: integer): RawByteString; overload;
/// after how many generated bytes Seed method would be called
// - default is 1 MB
property SeedAfterBytes: integer read fSeedAfterBytes;
/// how many PBKDF2_HMAC_SHA512 count is applied by Seed to the entropy
// - default is 16 rounds, which is more than enough for entropy gathering,
// since GetEntropy output comes from a SHAKE-256 generator in XOF mode
property SeedPBKDF2Rounds: cardinal read fSeedPBKDF2Rounds;
/// how many bits (128 or 256 - which is the default) are used for the AES
property AESKeySize: integer read fAESKeySize;
/// how many bytes this generator did compute
property TotalBytes: QWord read fTotalBytes;
end;
/// TAESPRNG-compatible class using Operating System pseudorandom source
// - may be used instead of TAESPRNG if a "standard" generator is required -
// you could override MainAESPRNG global variable
// - will call /dev/urandom under POSIX, and CryptGenRandom API on Windows
// - warning: may block on some BSD flavors, depending on /dev/urandom
// - from the cryptographic point of view, our TAESPRNG class doesn't suffer
// from the "black-box" approach of Windows, give consistent randomness
// over all supported cross-platform, and is indubitably faster
TAESPRNGSystem = class(TAESPRNG)
public
/// initialize the Operating System PRNG
constructor Create; reintroduce; virtual;
/// fill a TAESBlock with some pseudorandom data
// - this method is thread-safe
procedure FillRandom(out Block: TAESBlock); override;
/// fill a binary buffer with some pseudorandom data
// - this method is thread-safe
procedure FillRandom(Buffer: pointer; Len: integer); override;
/// called to force the internal generator to re-seed its private key
// - won't do anything for the Operating System pseudorandom source
procedure Seed; override;
end;
var
/// the shared TAESPRNG instance returned by TAESPRNG.Main class function
// - you may override this to a customized instance, e.g. if you expect
// a specific random generator to be used, like TAESPRNGSystem
// - all TAESPRNG.Fill() class functions will use this instance
MainAESPRNG: TAESPRNG;
{$ifdef HASINLINE}
/// defined globally to initialize MainAESPRNG for inlining TAESPRNG.Main
procedure SetMainAESPRNG;
{$endif}
/// low-level function returning some random binary using standard API
// - will call /dev/urandom under POSIX, and CryptGenRandom API on Windows,
// and fallback to SynCommons.FillRandom if the system is not supported
// - you should not have to call this procedure, but faster and safer TAESPRNG
procedure FillSystemRandom(Buffer: PByteArray; Len: integer; AllowBlocking: boolean);
type
PSHA1Digest = ^TSHA1Digest;
/// 160 bits memory block for SHA-1 hash digest storage
TSHA1Digest = packed array[0..19] of byte;
PSHA1 = ^TSHA1;
/// handle SHA-1 hashing
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance, e.g. for THMAC_SHA1
// - see TSynHasher if you expect to support more than one algorithm at runtime
{$ifdef UNICODE}TSHA1 = record{$else}TSHA1 = object{$endif}
private
Context: packed array[1..SHAContextSize] of byte;
public
/// initialize SHA-1 context for hashing
procedure Init;
/// update the SHA-1 context with some data
procedure Update(Buffer: pointer; Len: integer); overload;
/// update the SHA-1 context with some data
procedure Update(const Buffer: RawByteString); overload;
/// finalize and compute the resulting SHA-1 hash Digest of all data
// affected to Update() method
// - will also call Init to reset all internal temporary context, for safety
procedure Final(out Digest: TSHA1Digest; NoInit: boolean=false); overload;
/// finalize and compute the resulting SHA-1 hash Digest of all data
// affected to Update() method
// - will also call Init to reset all internal temporary context, for safety
function Final(NoInit: boolean=false): TSHA1Digest; overload; {$ifdef HASINLINE}inline;{$endif}
/// one method to rule them all
// - call Init, then Update(), then Final()
// - only Full() is Padlock-implemented - use this rather than Update()
procedure Full(Buffer: pointer; Len: integer; out Digest: TSHA1Digest);
end;
PSHA256Digest = ^TSHA256Digest;
/// 256 bits (32 bytes) memory block for SHA-256 hash digest storage
TSHA256Digest = THash256;
PSHA256 = ^TSHA256;
/// handle SHA-256 hashing
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance, e.g. for THMAC_SHA256
// - see TSynHasher if you expect to support more than one algorithm at runtime
{$ifdef UNICODE}TSHA256 = record{$else}TSHA256 = object{$endif}
private
Context: packed array[1..SHAContextSize] of byte;
public
/// initialize SHA-256 context for hashing
procedure Init;
/// update the SHA-256 context with some data
procedure Update(Buffer: pointer; Len: integer); overload;
/// update the SHA-256 context with some data
procedure Update(const Buffer: RawByteString); overload;
/// finalize and compute the resulting SHA-256 hash Digest of all data
// affected to Update() method
procedure Final(out Digest: TSHA256Digest; NoInit: boolean=false); overload;
/// finalize and compute the resulting SHA-256 hash Digest of all data
// affected to Update() method
function Final(NoInit: boolean=false): TSHA256Digest; overload; {$ifdef HASINLINE}inline;{$endif}
/// one method to rule them all
// - call Init, then Update(), then Final()
// - only Full() is Padlock-implemented - use this rather than Update()
procedure Full(Buffer: pointer; Len: integer; out Digest: TSHA256Digest);
end;
TSHA512Hash = record a, b, c, d, e, f, g, h: QWord; end;
PSHA384Digest = ^TSHA384Digest;
/// 384 bits (64 bytes) memory block for SHA-384 hash digest storage
TSHA384Digest = THash384;
/// handle SHA-384 hashing
// - it is in fact a TSHA512 truncated hash, with other initial hash values
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance, e.g. for THMAC_SHA384
// - see TSynHasher if you expect to support more than one algorithm at runtime
{$ifdef UNICODE}TSHA384 = record{$else}TSHA384 = object{$endif}
private
Hash: TSHA512Hash;
MLen: QWord;
Data: array[0..127] of byte;
Index: integer;
public
/// initialize SHA-384 context for hashing
procedure Init;
/// update the SHA-384 context with some data
procedure Update(Buffer: pointer; Len: integer); overload;
/// update the SHA-384 context with some data
procedure Update(const Buffer: RawByteString); overload;
/// finalize and compute the resulting SHA-384 hash Digest of all data
// affected to Update() method
// - will also call Init to reset all internal temporary context, for safety
procedure Final(out Digest: TSHA384Digest; NoInit: boolean=false); overload;
/// finalize and compute the resulting SHA-384 hash Digest of all data
// affected to Update() method
function Final(NoInit: boolean=false): TSHA384Digest; overload; {$ifdef HASINLINE}inline;{$endif}
/// one method to rule them all
// - call Init, then Update(), then Final()
procedure Full(Buffer: pointer; Len: integer; out Digest: TSHA384Digest);
end;
/// points to SHA-384 hashing instance
PSHA384 = ^TSHA384;
PSHA512Digest = ^TSHA512Digest;
/// 512 bits (64 bytes) memory block for SHA-512 hash digest storage
TSHA512Digest = THash512;
/// handle SHA-512 hashing
// - by design, this algorithm is expected to be much faster on 64-bit CPU,
// since all internal process involves QWord - but we included a SSE3 asm
// optimized version on 32-bit CPU under Windows and Linux, which is almost
// as fast as on plain x64, and even faster than SHA-256 and SHA-3
// - under x86/Delphi, plain pascal is 40MB/s, SSE3 asm 180MB/s
// - on x64, pascal Delphi is 150MB/s, and FPC is 190MB/s (thanks to native
// RorQWord intrinsic compiler function) - we also included a SSE4 asm version
// which outperforms other cryptographic hashes to more than 380MB/s
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance, e.g. for THMAC_SHA512
// - see TSynHasher if you expect to support more than one algorithm at runtime
{$ifdef UNICODE}TSHA512 = record{$else}TSHA512 = object{$endif}
private
Hash: TSHA512Hash;
MLen: QWord;
Data: array[0..127] of byte;
Index: integer;
public
/// initialize SHA-512 context for hashing
procedure Init;
/// update the SHA-512 context with some data
procedure Update(Buffer: pointer; Len: integer); overload;
/// update the SHA-512 context with some data
procedure Update(const Buffer: RawByteString); overload;
/// finalize and compute the resulting SHA-512 hash Digest of all data
// affected to Update() method
// - will also call Init to reset all internal temporary context, for safety
procedure Final(out Digest: TSHA512Digest; NoInit: boolean=false); overload;
/// finalize and compute the resulting SHA-512 hash Digest of all data
// affected to Update() method
function Final(NoInit: boolean=false): TSHA512Digest; overload; {$ifdef HASINLINE}inline;{$endif}
/// one method to rule them all
// - call Init, then Update(), then Final()
procedure Full(Buffer: pointer; Len: integer; out Digest: TSHA512Digest);
end;
/// points to SHA-512 hashing instance
PSHA512 = ^TSHA512;
/// SHA-3 instances, as defined by NIST Standard for Keccak sponge construction
TSHA3Algo = (SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE_128, SHAKE_256);
PSHA3 = ^TSHA3;
/// handle SHA-3 (Keccak) hashing
// - Keccak was the winner of the NIST hashing competition for a new hashing
// algorithm to provide an alternative to SHA-256. It became SHA-3 and was
// named by NIST a FIPS 180-4, then FIPS 202 hashing standard in 2015
// - by design, SHA-3 doesn't need to be encapsulated into a HMAC algorithm,
// since it already includes proper padding, so keys could be concatenated
// - this implementation is based on Wolfgang Ehrhardt's and Eric Grange's,
// with our own manually optimized x64 assembly
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance, e.g. after InitCypher
// - see TSynHasher if you expect to support more than one algorithm at runtime
{$ifdef UNICODE}TSHA3 = record{$else}TSHA3 = object{$endif}
private
Context: packed array[1..SHA3ContextSize] of byte;
public
/// initialize SHA-3 context for hashing
// - in practice, you may use SHA3_256 or SHA3_512 to return THash256
// or THash512 digests
procedure Init(Algo: TSHA3Algo);
/// update the SHA-3 context with some data
procedure Update(Buffer: pointer; Len: integer); overload;
/// update the SHA-3 context with some data
procedure Update(const Buffer: RawByteString); overload;
/// finalize and compute the resulting SHA-3 hash 256-bit Digest
procedure Final(out Digest: THash256; NoInit: boolean=false); overload;
/// finalize and compute the resulting SHA-3 hash 512-bit Digest
procedure Final(out Digest: THash512; NoInit: boolean=false); overload;
/// finalize and compute the resulting SHA-3 hash 256-bit Digest
function Final256(NoInit: boolean=false): THash256;
/// finalize and compute the resulting SHA-3 hash 512-bit Digest
function Final512(NoInit: boolean=false): THash512;
/// finalize and compute the resulting SHA-3 hash Digest
// - Digest destination buffer must contain enough bytes
// - default DigestBits=0 will write the default number of bits to Digest
// output memory buffer, according to the current TSHA3Algo
// - you can call this method several times, to use this SHA-3 hasher as
// "Extendable-Output Function" (XOF), e.g. for stream encryption (ensure
// NoInit is set to true, to enable recall)
procedure Final(Digest: pointer; DigestBits: integer=0; NoInit: boolean=false); overload;
/// compute a SHA-3 hash 256-bit Digest from a buffer, in one call
// - call Init, then Update(), then Final() using SHA3_256 into a THash256
procedure Full(Buffer: pointer; Len: integer; out Digest: THash256); overload;
/// compute a SHA-3 hash 512-bit Digest from a buffer, in one call
// - call Init, then Update(), then Final() using SHA3_512 into a THash512
procedure Full(Buffer: pointer; Len: integer; out Digest: THash512); overload;
/// compute a SHA-3 hash Digest from a buffer, in one call
// - call Init, then Update(), then Final() using the supplied algorithm
// - default DigestBits=0 will write the default number of bits to Digest
// output memory buffer, according to the specified TSHA3Algo
procedure Full(Algo: TSHA3Algo; Buffer: pointer; Len: integer;
Digest: pointer; DigestBits: integer=0); overload;
/// compute a SHA-3 hash hexadecimal Digest from a buffer, in one call
// - call Init, then Update(), then Final() using the supplied algorithm
// - default DigestBits=0 will write the default number of bits to Digest
// output memory buffer, according to the specified TSHA3Algo
function FullStr(Algo: TSHA3Algo; Buffer: pointer; Len: integer;
DigestBits: integer=0): RawUTF8;
/// uses SHA-3 in "Extendable-Output Function" (XOF) to cypher some content
// - there is no MAC stored in the resulting binary
// - Source and Dest will have the very same DataLen size in bytes,
// and Dest will be Source XORed with the XOF output, so encryption and
// decryption are just obtained by the same symmetric call
// - in this implementation, Source and Dest should point to two diverse buffers
// - for safety, the Key should be a secret value, pre-pended with a random
// salt/IV or a resource-specific identifier (e.g. a record ID or a S/N),
// to avoid reverse composition of the cypher from known content - note that
// concatenating keys with SHA-3 is as safe as computing a HMAC for SHA-2
procedure Cypher(Key, Source, Dest: pointer; KeyLen, DataLen: integer;
Algo: TSHA3Algo = SHAKE_256); overload;
/// uses SHA-3 in "Extendable-Output Function" (XOF) to cypher some content
// - this overloaded function works with RawByteString content
// - resulting string will have the very same size than the Source
// - XOF is implemented as a symmetrical algorithm: use this Cypher()
// method for both encryption and decryption of any buffer
function Cypher(const Key, Source: RawByteString; Algo: TSHA3Algo = SHAKE_256): RawByteString; overload;
/// uses SHA-3 in "Extendable-Output Function" (XOF) to cypher some content
// - prepare the instance to further Cypher() calls
// - you may reuse the very same TSHA3 instance by copying it to a local
// variable before calling this method (this copy is thread-safe)
// - works with RawByteString content
procedure InitCypher(Key: pointer; KeyLen: integer; Algo: TSHA3Algo = SHAKE_256); overload;
/// uses SHA-3 in "Extendable-Output Function" (XOF) to cypher some content
// - prepare the instance to further Cypher() calls
// - you may reuse the very same TSHA3 instance by copying it to a local
// variable before calling this method (this copy is thread-safe)
// - works with RawByteString content
procedure InitCypher(const Key: RawByteString; Algo: TSHA3Algo = SHAKE_256); overload;
/// uses SHA-3 in "Extendable-Output Function" (XOF) to cypher some content
// - this overloaded function expects the instance to have been prepared
// by previous InitCypher call
// - resulting Dest buffer will have the very same size than the Source
// - XOF is implemented as a symmetrical algorithm: use this Cypher()
// method for both encryption and decryption of any buffer
// - you can call this method several times, to work with a stream buffer;
// but for safety, you should eventually call Done
procedure Cypher(Source, Dest: pointer; DataLen: integer); overload;
/// uses SHA-3 in "Extendable-Output Function" (XOF) to cypher some content
// - this overloaded function expects the instance to have been prepared
// by previous InitCypher call
// - resulting string will have the very same size than the Source
// - XOF is implemented as a symmetrical algorithm: use this Cypher()
// method for both encryption and decryption of any buffer
// - you can call this method several times, to work with a stream buffer;
// but for safety, you should eventually call Done
function Cypher(const Source: RawByteString): RawByteString; overload;
/// returns the algorithm specified at Init()
function Algorithm: TSHA3Algo;
/// fill all used memory context with zeros, for safety
// - is necessary only when NoInit is set to true (e.g. after InitCypher)
procedure Done;
end;
/// 64 bytes buffer, used internally during HMAC process
TByte64 = array[0..15] of cardinal;
TMD5In = array[0..15] of cardinal;
PMD5In = ^TMD5In;
/// 128 bits memory block for MD5 hash digest storage
TMD5Digest = THash128;
PMD5Digest = ^TMD5Digest;
PMD5 = ^TMD5;
TMD5Buf = TBlock128;
/// handle MD5 hashing
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance
// - see TSynHasher if you expect to support more than one algorithm at runtime
// - even if MD5 is now seldom used, it is still faster than SHA alternatives,
// when you need a 128-bit cryptographic hash, but can afford some collisions
// - this implementation has optimized x86 and x64 assembly, for processing
// around 500MB/s, and a pure-pascal fallback code on other platforms
{$ifdef UNICODE}TMD5 = record{$else}TMD5 = object{$endif}
private
in_: TMD5In;
bytes: array[0..1] of cardinal;
public
buf: TMD5Buf;
/// initialize MD5 context for hashing
procedure Init;
/// update the MD5 context with some data
procedure Update(const buffer; Len: cardinal); overload;
/// update the MD5 context with some data
procedure Update(const Buffer: RawByteString); overload;
/// finalize the MD5 hash process
// - the resulting hash digest would be stored in buf public variable
procedure Finalize;
/// finalize and compute the resulting MD5 hash Digest of all data
// affected to Update() method
procedure Final(out result: TMD5Digest); overload;
/// finalize and compute the resulting MD5 hash Digest of all data
// affected to Update() method
function Final: TMD5Digest; overload;
/// one method to rule them all
// - call Init, then Update(), then Final()
procedure Full(Buffer: pointer; Len: integer; out Digest: TMD5Digest);
end;
/// handle RC4 encryption/decryption
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance
// - you can also restore and backup any previous state of the RC4 encryption
// by copying the whole TRC4 variable into another (stack-allocated) variable
{$ifdef UNICODE}TRC4 = record{$else}TRC4 = object{$endif}
private
{$ifdef CPUINTEL}
state: array[byte] of PtrInt; // PtrInt=270MB/s byte=240MB/s on x86
{$else}
state: array[byte] of byte; // on ARM, keep the CPU cache usage low
{$endif}
currI, currJ: PtrInt;
public
/// initialize the RC4 encryption/decryption
// - KeyLen is in bytes, and should be within 1..255 range
procedure Init(const aKey; aKeyLen: integer);
/// initialize RC4-drop[3072] encryption/decryption after SHA-3 hashing
// - will use SHAKE-128 generator in XOF mode to generate a 256 bytes key,
// then drop the first 3072 bytes from the RC4 stream
// - this initializer is much safer than plain Init, so should be considered
// for any use on RC4 for new projects - even if AES-NI is 2 times faster,
// and safer SHAKE-128 operates in XOF mode at a similar speed range
procedure InitSHA3(const aKey; aKeyLen: integer);
/// drop the next Count bytes from the RC4 cypher state
// - may be used in Stream mode, or to initialize in RC4-drop[n] mode
procedure Drop(Count: cardinal);
/// perform the RC4 cypher encryption/decryption on a buffer
// - each call to this method shall be preceeded with an Init() call
// - RC4 is a symmetrical algorithm: use this Encrypt() method
// for both encryption and decryption of any buffer
procedure Encrypt(const BufIn; var BufOut; Count: cardinal);
{$ifdef HASINLINE}inline;{$endif}
/// perform the RC4 cypher encryption/decryption on a buffer
// - each call to this method shall be preceeded with an Init() call
// - RC4 is a symmetrical algorithm: use this EncryptBuffer() method
// for both encryption and decryption of any buffer
procedure EncryptBuffer(BufIn, BufOut: PByte; Count: cardinal);
end;
{$A-} { packed memory structure }
/// internal header for storing our AES data with salt and CRC
// - memory size matches an TAESBlock on purpose, for direct encryption
{$ifdef UNICODE}TAESFullHeader = record{$else}TAESFullHeader = object{$endif}
public
/// Len before compression (if any)
OriginalLen,
/// Len before AES encoding
SourceLen,
/// Random Salt for better encryption
SomeSalt,
/// CRC from header
HeaderCheck: cardinal;
/// computes the Key checksum, using Adler32 algorithm
function Calc(const Key; KeySize: cardinal): cardinal;
end;
{$A+}
PAESFull = ^TAESFull;
/// AES and XOR encryption object for easy direct memory or stream access
// - calls internaly TAES objet methods, and handle memory and streams for best speed
// - a TAESFullHeader is encrypted at the begining, allowing fast Key validation,
// but the resulting stream is not compatible with raw TAES object
{$ifdef UNICODE}TAESFull = record{$else}TAESFull = object{$endif}
public
/// header, stored at the beginning of struct -> 16-byte aligned
Head: TAESFullHeader;
/// this memory stream is used in case of EncodeDecode(outStream=bOut=nil)
// method call
outStreamCreated: TMemoryStream;
/// main method of AES or XOR cypher/uncypher
// - return out size, -1 if error on decoding (Key not correct)
// - valid KeySize: 0=nothing, 32=xor, 128,192,256=AES
// - if outStream is TMemoryStream -> auto-reserve space (no Realloc:)
// - for normal usage, you just have to Assign one In and one Out
// - if outStream AND bOut are both nil, an outStream is created via
// THeapMemoryStream.Create
// - if Padlock is used, 16-byte alignment is forced (via tmp buffer if necessary)
// - if Encrypt -> OriginalLen can be used to store unCompressed Len
function EncodeDecode(const Key; KeySize, inLen: cardinal; Encrypt: boolean;
inStream, outStream: TStream; bIn, bOut: pointer; OriginalLen: cardinal=0): integer;
end;
/// AES encryption stream
// - encrypt the Data on the fly, in a compatible way with AES() - last bytes
// are coded with XOR (not compatible with TAESFull format)
// - not optimized for small blocks -> ok if used AFTER TBZCompressor/TZipCompressor
// - warning: Write() will crypt Buffer memory in place -> use AFTER T*Compressor
TAESWriteStream = class(TStream)
public
Adler, // CRC from uncrypted compressed data - for Key check
DestSize: cardinal;
private
Dest: TStream;
Buf: TAESBlock; // very small buffer for remainging 0..15 bytes
BufCount: integer; // number of pending bytes (0..15) in Buf
AES: TAES;
NoCrypt: boolean; // if KeySize=0
public
/// initialize the AES encryption stream for an output stream (e.g.
// a TMemoryStream or a TFileStream)
constructor Create(outStream: TStream; const Key; KeySize: cardinal);
/// finalize the AES encryption stream
// - internaly call the Finish method
destructor Destroy; override;
/// read some data is not allowed -> this method will raise an exception on call
function Read(var Buffer; Count: Longint): Longint; override;
/// append some data to the outStream, after encryption
function Write(const Buffer; Count: Longint): Longint; override;
/// read some data is not allowed -> this method will raise an exception on call
function Seek(Offset: Longint; Origin: Word): Longint; override;
/// write pending data
// - should always be called before closeing the outStream (some data may
// still be in the internal buffers)
procedure Finish;
end;
/// overwrite a 64-byte buffer with zeros
// - may be used to cleanup stack-allocated content
// ! ... finally FillZero(temp); end;
procedure FillZero(var hash: TByte64); overload;
/// overwrite a SHA-1 digest buffer with zeros
// - may be used to cleanup stack-allocated content
// ! ... finally FillZero(temp); end;
procedure FillZero(var hash: TSHA1Digest); overload;
/// compare two SHA-1 digest buffers
function IsEqual(const A,B: TSHA1Digest): boolean; overload;
/// direct MD5 hash calculation of some data
function MD5Buf(const Buffer; Len: Cardinal): TMD5Digest;
/// direct MD5 hash calculation of some data (string-encoded)
// - result is returned in hexadecimal format
function MD5(const s: RawByteString): RawUTF8;
/// direct SHA-1 hash calculation of some data (string-encoded)
// - result is returned in hexadecimal format
function SHA1(const s: RawByteString): RawUTF8;
/// direct SHA-384 hash calculation of some data (string-encoded)
// - result is returned in hexadecimal format
function SHA384(const s: RawByteString): RawUTF8;
/// direct SHA-512 hash calculation of some data (string-encoded)
// - result is returned in hexadecimal format
function SHA512(const s: RawByteString): RawUTF8;
type
/// compute the HMAC message authentication code using SHA-1 as hash function
// - you may use HMAC_SHA1() overloaded functions for one-step process
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance via Compute(), e.g. for fast PBKDF2
{$ifdef UNICODE}THMAC_SHA1 = record{$else}THMAC_SHA1 = object{$endif}
private
sha: TSHA1;
step7data: TByte64;
public
/// prepare the HMAC authentication with the supplied key
// - content of this record is stateless, so you can prepare a HMAC for a
// key using Init, then copy this THMAC_SHA1 instance to a local variable,
// and use this local thread-safe copy for actual HMAC computing
procedure Init(key: pointer; keylen: integer);
/// call this method for each continuous message block
// - iterate over all message blocks, then call Done to retrieve the HMAC
procedure Update(msg: pointer; msglen: integer);
/// computes the HMAC of all supplied message according to the key
procedure Done(out result: TSHA1Digest; NoInit: boolean=false); overload;
/// computes the HMAC of all supplied message according to the key
procedure Done(out result: RawUTF8; NoInit: boolean=false); overload;
/// computes the HMAC of the supplied message according to the key
// - expects a previous call on Init() to setup the shared key
// - similar to a single Update(msg,msglen) followed by Done, but re-usable
// - this method is thread-safe on any shared THMAC_SHA1 instance
procedure Compute(msg: pointer; msglen: integer; out result: TSHA1Digest);
end;
/// points to a HMAC message authentication context using SHA-1
PHMAC_SHA1 = ^THMAC_SHA1;
/// compute the HMAC message authentication code using SHA-1 as hash function
procedure HMAC_SHA1(const key,msg: RawByteString; out result: TSHA1Digest); overload;
/// compute the HMAC message authentication code using SHA-1 as hash function
procedure HMAC_SHA1(const key: TSHA1Digest; const msg: RawByteString;
out result: TSHA1Digest); overload;
/// compute the HMAC message authentication code using SHA-1 as hash function
procedure HMAC_SHA1(key,msg: pointer; keylen,msglen: integer;
out result: TSHA1Digest); overload;
/// compute the PBKDF2 derivation of a password using HMAC over SHA-1
// - this function expect the resulting key length to match SHA-1 digest size
procedure PBKDF2_HMAC_SHA1(const password,salt: RawByteString; count: Integer;
out result: TSHA1Digest);
type
/// compute the HMAC message authentication code using SHA-384 as hash function
// - you may use HMAC_SHA384() overloaded functions for one-step process
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance via Compute(), e.g. for fast PBKDF2
{$ifdef UNICODE}THMAC_SHA384 = record{$else}THMAC_SHA384 = object{$endif}
private
sha: TSHA384;
step7data: array[0..31] of cardinal;
public
/// prepare the HMAC authentication with the supplied key
// - content of this record is stateless, so you can prepare a HMAC for a
// key using Init, then copy this THMAC_SHA384 instance to a local variable,
// and use this local thread-safe copy for actual HMAC computing
procedure Init(key: pointer; keylen: integer);
/// call this method for each continuous message block
// - iterate over all message blocks, then call Done to retrieve the HMAC
procedure Update(msg: pointer; msglen: integer);
/// computes the HMAC of all supplied message according to the key
procedure Done(out result: TSHA384Digest; NoInit: boolean=false); overload;
/// computes the HMAC of all supplied message according to the key
procedure Done(out result: RawUTF8; NoInit: boolean=false); overload;
/// computes the HMAC of the supplied message according to the key
// - expects a previous call on Init() to setup the shared key
// - similar to a single Update(msg,msglen) followed by Done, but re-usable
// - this method is thread-safe on any shared THMAC_SHA384 instance
procedure Compute(msg: pointer; msglen: integer; out result: TSHA384Digest);
end;
/// points to a HMAC message authentication context using SHA-384
PHMAC_SHA384 = ^THMAC_SHA384;
/// compute the HMAC message authentication code using SHA-384 as hash function
procedure HMAC_SHA384(const key,msg: RawByteString; out result: TSHA384Digest); overload;
/// compute the HMAC message authentication code using SHA-384 as hash function
procedure HMAC_SHA384(const key: TSHA384Digest; const msg: RawByteString;
out result: TSHA384Digest); overload;
/// compute the HMAC message authentication code using SHA-384 as hash function
procedure HMAC_SHA384(key,msg: pointer; keylen,msglen: integer;
out result: TSHA384Digest); overload;
/// compute the PBKDF2 derivation of a password using HMAC over SHA-384
// - this function expect the resulting key length to match SHA-384 digest size
procedure PBKDF2_HMAC_SHA384(const password,salt: RawByteString; count: Integer;
out result: TSHA384Digest);
type
/// compute the HMAC message authentication code using SHA-512 as hash function
// - you may use HMAC_SHA512() overloaded functions for one-step process
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance via Compute(), e.g. for fast PBKDF2
{$ifdef UNICODE}THMAC_SHA512 = record{$else}THMAC_SHA512 = object{$endif}
private
sha: TSHA512;
step7data: array[0..31] of cardinal;
public
/// prepare the HMAC authentication with the supplied key
// - content of this record is stateless, so you can prepare a HMAC for a
// key using Init, then copy this THMAC_SHA512 instance to a local variable,
// and use this local thread-safe copy for actual HMAC computing
procedure Init(key: pointer; keylen: integer);
/// call this method for each continuous message block
// - iterate over all message blocks, then call Done to retrieve the HMAC
procedure Update(msg: pointer; msglen: integer);
/// computes the HMAC of all supplied message according to the key
procedure Done(out result: TSHA512Digest; NoInit: boolean=false); overload;
/// computes the HMAC of all supplied message according to the key
procedure Done(out result: RawUTF8; NoInit: boolean=false); overload;
/// computes the HMAC of the supplied message according to the key
// - expects a previous call on Init() to setup the shared key
// - similar to a single Update(msg,msglen) followed by Done, but re-usable
// - this method is thread-safe on any shared THMAC_SHA512 instance
procedure Compute(msg: pointer; msglen: integer; out result: TSHA512Digest);
end;
/// points to a HMAC message authentication context using SHA-512
PHMAC_SHA512 = ^THMAC_SHA512;
/// compute the HMAC message authentication code using SHA-512 as hash function
procedure HMAC_SHA512(const key,msg: RawByteString; out result: TSHA512Digest); overload;
/// compute the HMAC message authentication code using SHA-512 as hash function
procedure HMAC_SHA512(const key: TSHA512Digest; const msg: RawByteString;
out result: TSHA512Digest); overload;
/// compute the HMAC message authentication code using SHA-512 as hash function
procedure HMAC_SHA512(key,msg: pointer; keylen,msglen: integer;
out result: TSHA512Digest); overload;
/// compute the PBKDF2 derivation of a password using HMAC over SHA-512
// - this function expect the resulting key length to match SHA-512 digest size
procedure PBKDF2_HMAC_SHA512(const password,salt: RawByteString; count: Integer;
out result: TSHA512Digest);
/// direct SHA-256 hash calculation of some data (string-encoded)
// - result is returned in hexadecimal format
function SHA256(const s: RawByteString): RawUTF8; overload;
/// direct SHA-256 hash calculation of some binary data
// - result is returned in hexadecimal format
function SHA256(Data: pointer; Len: integer): RawUTF8; overload;
/// direct SHA-256 hash calculation of some binary data
// - result is returned in TSHA256Digest binary format
// - since the result would be stored temporarly in the stack, it may be
// safer to use an explicit TSHA256Digest variable, which would be filled
// with zeros by a ... finally FillZero(
function SHA256Digest(Data: pointer; Len: integer): TSHA256Digest; overload;
/// direct SHA-256 hash calculation of some binary data
// - result is returned in TSHA256Digest binary format
// - since the result would be stored temporarly in the stack, it may be
// safer to use an explicit TSHA256Digest variable, which would be filled
// with zeros by a ... finally FillZero(
function SHA256Digest(const Data: RawByteString): TSHA256Digest; overload;
/// direct SHA-256 hash calculation of some data (string-encoded)
// - result is returned in hexadecimal format
// - this procedure has a weak password protection: small incoming data
// is append to some salt, in order to have at least a 256 bytes long hash:
// such a feature improve security for small passwords, e.g.
// - note that this algorithm is proprietary, and less secure (and standard)
// than the PBKDF2 algorithm, so is there only for backward compatibility of
// existing code: use PBKDF2_HMAC_SHA256 or similar functions for password
// derivation
procedure SHA256Weak(const s: RawByteString; out Digest: TSHA256Digest);
type
/// compute the HMAC message authentication code using SHA-256 as hash function
// - you may use HMAC_SHA256() overloaded functions for one-step process
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance via Compute(), e.g. for fast PBKDF2
{$ifdef UNICODE}THMAC_SHA256 = record{$else}THMAC_SHA256 = object{$endif}
private
sha: TSha256;
step7data: TByte64;
public
/// prepare the HMAC authentication with the supplied key
// - content of this record is stateless, so you can prepare a HMAC for a
// key using Init, then copy this THMAC_SHA256 instance to a local variable,
// and use this local thread-safe copy for actual HMAC computing
procedure Init(key: pointer; keylen: integer);
/// call this method for each continuous message block
// - iterate over all message blocks, then call Done to retrieve the HMAC
procedure Update(msg: pointer; msglen: integer); overload;
/// call this method for each continuous message block
// - iterate over all message blocks, then call Done to retrieve the HMAC
procedure Update(const msg: THash128); overload;
/// call this method for each continuous message block
// - iterate over all message blocks, then call Done to retrieve the HMAC
procedure Update(const msg: THash256); overload;
/// call this method for each continuous message block
// - iterate over all message blocks, then call Done to retrieve the HMAC
procedure Update(const msg: RawByteString); overload;
/// computes the HMAC of all supplied message according to the key
procedure Done(out result: TSHA256Digest; NoInit: boolean=false); overload;
/// computes the HMAC of all supplied message according to the key
procedure Done(out result: RawUTF8; NoInit: boolean=false); overload;
/// computes the HMAC of the supplied message according to the key
// - expects a previous call on Init() to setup the shared key
// - similar to a single Update(msg,msglen) followed by Done, but re-usable
// - this method is thread-safe on any shared THMAC_SHA256 instance
procedure Compute(msg: pointer; msglen: integer; out result: TSHA256Digest);
end;
/// points to a HMAC message authentication context using SHA-256
PHMAC_SHA256 = ^THMAC_SHA256;
/// compute the HMAC message authentication code using SHA-256 as hash function
procedure HMAC_SHA256(const key,msg: RawByteString; out result: TSHA256Digest); overload;
/// compute the HMAC message authentication code using SHA-256 as hash function
procedure HMAC_SHA256(const key: TSHA256Digest; const msg: RawByteString;
out result: TSHA256Digest); overload;
/// compute the HMAC message authentication code using SHA-256 as hash function
procedure HMAC_SHA256(key,msg: pointer; keylen,msglen: integer; out result: TSHA256Digest); overload;
/// compute the PBKDF2 derivation of a password using HMAC over SHA-256
// - this function expect the resulting key length to match SHA-256 digest size
procedure PBKDF2_HMAC_SHA256(const password,salt: RawByteString; count: Integer;
out result: TSHA256Digest; const saltdefault: RawByteString=''); overload;
/// compute the PBKDF2 derivation of a password using HMAC over SHA-256, into
// several 256-bit items, so can be used to return any size of output key
// - this function expect the result array to have the expected output length
// - allows resulting key length to be more than one SHA-256 digest size, e.g.
// to be used for both Encryption and MAC
procedure PBKDF2_HMAC_SHA256(const password,salt: RawByteString; count: Integer;
var result: THash256DynArray; const saltdefault: RawByteString=''); overload;
/// direct SHA-3 hash calculation of some data (string-encoded)
// - result is returned in hexadecimal format
// - default DigestBits=0 will write the default number of bits to Digest
// output memory buffer, according to the specified TSHA3Algo
function SHA3(Algo: TSHA3Algo; const s: RawByteString;
DigestBits: integer=0): RawUTF8; overload;
/// direct SHA-3 hash calculation of some binary buffer
// - result is returned in hexadecimal format
// - default DigestBits=0 will write the default number of bits to Digest
// output memory buffer, according to the specified TSHA3Algo
function SHA3(Algo: TSHA3Algo; Buffer: pointer; Len: integer;
DigestBits: integer=0): RawUTF8; overload;
/// safe key derivation using iterated SHA-3 hashing
// - you can use SHA3_224, SHA3_256, SHA3_384, SHA3_512 algorithm to fill
// the result buffer with the default sized derivated key of 224,256,384 or 512
// bits (leaving resultbytes = 0)
// - or you may select SHAKE_128 or SHAKE_256, and specify any custom key size
// in resultbytes (used e.g. by PBKDF2_SHA3_Crypt)
procedure PBKDF2_SHA3(algo: TSHA3Algo; const password,salt: RawByteString;
count: Integer; result: PByte; resultbytes: integer=0);
/// encryption/decryption of any data using iterated SHA-3 hashing key derivation
// - specified algo is expected to be SHAKE_128 or SHAKE_256
// - expected the supplied data buffer to be small - for bigger content, consider
// using TSHA.Cypher after 256-bit PBKDF2_SHA3 key derivation
procedure PBKDF2_SHA3_Crypt(algo: TSHA3Algo; const password,salt: RawByteString;
count: Integer; var data: RawByteString);
type
/// the HMAC/SHA-3 algorithms known by TSynSigner
TSignAlgo = (
saSha1, saSha256, saSha384, saSha512,
saSha3224, saSha3256, saSha3384, saSha3512, saSha3S128, saSha3S256);
/// JSON-serialization ready object as used by TSynSigner.PBKDF2 overloaded methods
// - default value for unspecified parameters will be SHAKE_128 with
// rounds=1000 and a fixed salt
TSynSignerParams = packed record
algo: TSignAlgo;
secret,salt: RawUTF8;
rounds: integer;
end;
/// a generic wrapper object to handle digital HMAC-SHA-2/SHA-3 signatures
// - used e.g. to implement TJWTSynSignerAbstract
{$ifdef UNICODE}TSynSigner = record{$else}TSynSigner = object{$endif}
private
ctxt: packed array[1..SHA3ContextSize] of byte; // enough space for all
fSignatureSize: integer;
fAlgo: TSignAlgo;
public
/// initialize the digital HMAC/SHA-3 signing context with some secret text
procedure Init(aAlgo: TSignAlgo; const aSecret: RawUTF8); overload;
/// initialize the digital HMAC/SHA-3 signing context with some secret binary
procedure Init(aAlgo: TSignAlgo; aSecret: pointer; aSecretLen: integer); overload;
/// initialize the digital HMAC/SHA-3 signing context with PBKDF2 safe
// iterative key derivation of a secret salted text
procedure Init(aAlgo: TSignAlgo; const aSecret, aSalt: RawUTF8;
aSecretPBKDF2Rounds: integer; aPBKDF2Secret: PHash512Rec=nil); overload;
/// process some message content supplied as memory buffer
procedure Update(aBuffer: pointer; aLen: integer); overload;
/// process some message content supplied as string
procedure Update(const aBuffer: RawByteString); overload; {$ifdef HASINLINE}inline;{$endif}
/// returns the computed digital signature as lowercase hexadecimal text
function Final: RawUTF8; overload;
/// returns the raw computed digital signature
// - SignatureSize bytes will be written: use Signature.Lo/h0/b3/b accessors
procedure Final(out aSignature: THash512Rec; aNoInit: boolean=false); overload;
/// one-step digital signature of a buffer as lowercase hexadecimal string
function Full(aAlgo: TSignAlgo; const aSecret: RawUTF8;
aBuffer: Pointer; aLen: integer): RawUTF8; overload;
/// one-step digital signature of a buffer with PBKDF2 derivation
function Full(aAlgo: TSignAlgo; const aSecret, aSalt: RawUTF8;
aSecretPBKDF2Rounds: integer; aBuffer: Pointer; aLen: integer): RawUTF8; overload;
/// convenient wrapper to perform PBKDF2 safe iterative key derivation
procedure PBKDF2(aAlgo: TSignAlgo; const aSecret, aSalt: RawUTF8;
aSecretPBKDF2Rounds: integer; out aDerivatedKey: THash512Rec); overload;
/// convenient wrapper to perform PBKDF2 safe iterative key derivation
procedure PBKDF2(const aParams: TSynSignerParams; out aDerivatedKey: THash512Rec); overload;
/// convenient wrapper to perform PBKDF2 safe iterative key derivation
// - accept as input a TSynSignerParams serialized as JSON object
procedure PBKDF2(aParamsJSON: PUTF8Char; aParamsJSONLen: integer;
out aDerivatedKey: THash512Rec; const aDefaultSalt: RawUTF8='I6sWioAidNnhXO9BK';
aDefaultAlgo: TSignAlgo=saSha3S128); overload;
/// convenient wrapper to perform PBKDF2 safe iterative key derivation
// - accept as input a TSynSignerParams serialized as JSON object
procedure PBKDF2(const aParamsJSON: RawUTF8; out aDerivatedKey: THash512Rec;
const aDefaultSalt: RawUTF8='I6sWioAidNnhXO9BK'; aDefaultAlgo: TSignAlgo=saSha3S128); overload;
/// prepare a TAES object with the key derivated via a PBKDF2() call
// - aDerivatedKey is defined as "var", since it will be zeroed after use
procedure AssignTo(var aDerivatedKey: THash512Rec; out aAES: TAES; aEncrypt: boolean);
/// fill the intenral context with zeros, for security
procedure Done;
/// the algorithm used for digitial signature
property Algo: TSignAlgo read fAlgo;
/// the size, in bytes, of the digital signature of this algorithm
// - potential values are 20, 28, 32, 48 and 64
property SignatureSize: integer read fSignatureSize;
end;
/// reference to TSynSigner wrapper object
PSynSigner = ^TSynSigner;
/// hash algorithms available for HashFile/HashFull functions and TSynHasher object
THashAlgo = (hfMD5, hfSHA1, hfSHA256, hfSHA384, hfSHA512, hfSHA3_256, hfSHA3_512);
/// set of algorithms available for HashFile/HashFull functions and TSynHasher object
THashAlgos = set of THashAlgo;
/// convenient multi-algorithm hashing wrapper
// - as used e.g. by HashFile/HashFull functions
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance
{$ifdef UNICODE}TSynHasher = record{$else}TSynHasher = object{$endif}
private
fAlgo: THashAlgo;
ctxt: array[1..SHA3ContextSize] of byte; // enough space for all algorithms
public
/// initialize the internal hashing structure for a specific algorithm
// - returns false on unknown/unsupported algorithm
function Init(aAlgo: THashAlgo): boolean;
/// hash the supplied memory buffer
procedure Update(aBuffer: Pointer; aLen: integer); overload;
/// hash the supplied string content
procedure Update(const aBuffer: RawByteString); overload; {$ifdef HASINLINE}inline;{$endif}
/// returns the resulting hash as lowercase hexadecimal string
function Final: RawUTF8;
/// one-step hash computation of a buffer as lowercase hexadecimal string
function Full(aAlgo: THashAlgo; aBuffer: Pointer; aLen: integer): RawUTF8;
/// the hash algorithm used by this instance
property Algo: THashAlgo read fAlgo;
end;
/// compute the hexadecimal hash of any (big) file
// - using a temporary buffer of 1MB for the sequential reading
function HashFile(const aFileName: TFileName; aAlgo: THashAlgo): RawUTF8; overload;
/// compute the hexadecimal hashe(s) of one file, as external .md5/.sha256/.. files
// - reading the file once in memory, then apply all algorithms on it and
// generate the text hash files in the very same folder
procedure HashFile(const aFileName: TFileName; aAlgos: THashAlgos); overload;
/// one-step hash computation of a buffer as lowercase hexadecimal string
function HashFull(aAlgo: THashAlgo; aBuffer: Pointer; aLen: integer): RawUTF8;
/// compute the HMAC message authentication code using crc256c as hash function
// - HMAC over a non cryptographic hash function like crc256c is known to be
// safe as MAC, if the supplied key comes e.g. from cryptographic HMAC_SHA256
// - performs two crc32c hashes, so SSE 4.2 gives more than 2.2 GB/s on a Core i7
procedure HMAC_CRC256C(key,msg: pointer; keylen,msglen: integer; out result: THash256); overload;
/// compute the HMAC message authentication code using crc256c as hash function
// - HMAC over a non cryptographic hash function like crc256c is known to be
// safe as MAC, if the supplied key comes e.g. from cryptographic HMAC_SHA256
// - performs two crc32c hashes, so SSE 4.2 gives more than 2.2 GB/s on a Core i7
procedure HMAC_CRC256C(const key: THash256; const msg: RawByteString; out result: THash256); overload;
/// compute the HMAC message authentication code using crc256c as hash function
// - HMAC over a non cryptographic hash function like crc256c is known to be
// safe as MAC, if the supplied key comes e.g. from cryptographic HMAC_SHA256
// - performs two crc32c hashes, so SSE 4.2 gives more than 2.2 GB/s on a Core i7
procedure HMAC_CRC256C(const key,msg: RawByteString; out result: THash256); overload;
type
/// compute the HMAC message authentication code using crc32c as hash function
// - HMAC over a non cryptographic hash function like crc32c is known to be a
// safe enough MAC, if the supplied key comes e.g. from cryptographic HMAC_SHA256
// - SSE 4.2 will let MAC be computed at 4 GB/s on a Core i7
// - you may use HMAC_CRC32C() overloaded functions for one-step process
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance via Compute()
{$ifdef UNICODE}THMAC_CRC32C = record{$else}THMAC_CRC32C = object{$endif}
private
seed: cardinal;
step7data: TByte64;
public
/// prepare the HMAC authentication with the supplied key
// - consider using Compute to re-use a prepared HMAC instance
procedure Init(key: pointer; keylen: integer); overload;
/// prepare the HMAC authentication with the supplied key
// - consider using Compute to re-use a prepared HMAC instance
procedure Init(const key: RawByteString); overload;
/// call this method for each continuous message block
// - iterate over all message blocks, then call Done to retrieve the HMAC
procedure Update(msg: pointer; msglen: integer); overload;
{$ifdef HASINLINE}inline;{$endif}
/// call this method for each continuous message block
// - iterate over all message blocks, then call Done to retrieve the HMAC
procedure Update(const msg: RawByteString); overload;
{$ifdef HASINLINE}inline;{$endif}
/// computes the HMAC of all supplied message according to the key
function Done(NoInit: boolean=false): cardinal;
{$ifdef HASINLINE}inline;{$endif}
/// computes the HMAC of the supplied message according to the key
// - expects a previous call on Init() to setup the shared key
// - similar to a single Update(msg,msglen) followed by Done, but re-usable
// - this method is thread-safe
function Compute(msg: pointer; msglen: integer): cardinal;
end;
/// points to HMAC message authentication code using crc32c as hash function
PHMAC_CRC32C= ^THMAC_CRC32C;
/// compute the HMAC message authentication code using crc32c as hash function
// - HMAC over a non cryptographic hash function like crc32c is known to be a
// safe enough MAC, if the supplied key comes e.g. from cryptographic HMAC_SHA256
// - SSE 4.2 will let MAC be computed at 4 GB/s on a Core i7
function HMAC_CRC32C(key,msg: pointer; keylen,msglen: integer): cardinal; overload;
/// compute the HMAC message authentication code using crc32c as hash function
// - HMAC over a non cryptographic hash function like crc32c is known to be a
// safe enough MAC, if the supplied key comes e.g. from cryptographic HMAC_SHA256
// - SSE 4.2 will let MAC be computed at 4 GB/s on a Core i7
function HMAC_CRC32C(const key: THash256; const msg: RawByteString): cardinal; overload;
/// compute the HMAC message authentication code using crc32c as hash function
// - HMAC over a non cryptographic hash function like crc32c is known to be a
// safe enough MAC, if the supplied key comes e.g. from cryptographic HMAC_SHA256
// - SSE 4.2 will let MAC be computed at 4 GB/s on a Core i7
function HMAC_CRC32C(const key,msg: RawByteString): cardinal; overload;
/// direct Encrypt/Decrypt of data using the TAES class
// - last bytes (not part of 16 bytes blocks) are not crypted by AES, but with XOR
procedure AES(const Key; KeySize: cardinal; buffer: pointer; Len: Integer; Encrypt: boolean); overload;
/// direct Encrypt/Decrypt of data using the TAES class
// - last bytes (not part of 16 bytes blocks) are not crypted by AES, but with XOR
procedure AES(const Key; KeySize: cardinal; bIn, bOut: pointer; Len: Integer; Encrypt: boolean); overload;
/// direct Encrypt/Decrypt of data using the TAES class
// - last bytes (not part of 16 bytes blocks) are not crypted by AES, but with XOR
function AES(const Key; KeySize: cardinal; const s: RawByteString; Encrypt: boolean): RawByteString; overload;
/// direct Encrypt/Decrypt of data using the TAES class
// - last bytes (not part of 16 bytes blocks) are not crypted by AES, but with XOR
function AES(const Key; KeySize: cardinal; buffer: pointer; Len: cardinal; Stream: TStream; Encrypt: boolean): boolean; overload;
/// AES and XOR encryption using the TAESFull format
// - outStream will be larger/smaller than Len (full AES encrypted)
// - returns true if OK
function AESFull(const Key; KeySize: cardinal; bIn: pointer; Len: Integer;
outStream: TStream; Encrypt: boolean; OriginalLen: Cardinal=0): boolean; overload;
/// AES and XOR encryption using the TAESFull format
// - bOut must be at least bIn+32/Encrypt bIn-16/Decrypt
// - returns outLength, -1 if error
function AESFull(const Key; KeySize: cardinal; bIn, bOut: pointer; Len: Integer;
Encrypt: boolean; OriginalLen: Cardinal=0): integer; overload;
/// AES and XOR decryption check using the TAESFull format
// - return true if begining of buff contains true AESFull encrypted data with this Key
// - if not KeySize in [128,192,256] -> use fast and efficient Xor Cypher
function AESFullKeyOK(const Key; KeySize: cardinal; buff: pointer): boolean;
/// AES encryption using the TAES format with a supplied SHA-256 password
// - last bytes (not part of 16 bytes blocks) are not crypted by AES, but with XOR
procedure AESSHA256(Buffer: pointer; Len: integer; const Password: RawByteString; Encrypt: boolean); overload;
/// AES encryption using the TAES format with a supplied SHA-256 password
// - last bytes (not part of 16 bytes blocks) are not crypted by AES, but with XOR
procedure AESSHA256(bIn, bOut: pointer; Len: integer; const Password: RawByteString; Encrypt: boolean); overload;
/// AES encryption using the TAES format with a supplied SHA-256 password
// - last bytes (not part of 16 bytes blocks) are not crypted by AES, but with XOR
function AESSHA256(const s, Password: RawByteString; Encrypt: boolean): RawByteString; overload;
/// AES encryption using the TAESFull format with a supplied SHA-256 password
// - outStream will be larger/smaller than Len: this is a full AES version with
// a triming TAESFullHeader at the beginning
procedure AESSHA256Full(bIn: pointer; Len: Integer; outStream: TStream; const Password: RawByteString; Encrypt: boolean); overload;
var
/// salt for CryptDataForCurrentUser function
// - is filled with some random bytes by default, but you may override
// it for a set of custom processes calling CryptDataForCurrentUser
CryptProtectDataEntropy: THash256 = (
$19,$8E,$BA,$52,$FA,$D6,$56,$99,$7B,$73,$1B,$D0,$8B,$3A,$95,$AB,
$94,$63,$C2,$C0,$78,$05,$9C,$8B,$85,$B7,$A1,$E3,$ED,$93,$27,$18);
{$ifdef MSWINDOWS}
/// protect some data for the current user, using Windows DPAPI
// - the application can specify a secret salt text, which should reflect the
// current execution context, to ensure nobody could decrypt the data without
// knowing this application-specific AppSecret value
// - will use CryptProtectData DPAPI function call under Windows
// - see https://msdn.microsoft.com/en-us/library/ms995355
// - this function is Windows-only, could be slow, and you don't know which
// algorithm is really used on your system, so using CryptDataForCurrentUser()
// may be a better (and cross-platform) alternative
// - also note that DPAPI has been closely reverse engineered - see e.g.
// https://www.passcape.com/index.php?section=docsys&cmd=details&id=28
function CryptDataForCurrentUserDPAPI(const Data,AppSecret: RawByteString; Encrypt: boolean): RawByteString;
{$endif}
/// protect some data via AES-256-CFB and a secret known by the current user only
// - the application can specify a secret salt text, which should reflect the
// current execution context, to ensure nobody could decrypt the data without
// knowing this application-specific AppSecret value
// - here data is cyphered using a random secret key, stored in a file located in
// ! GetSystemPath(spUserData)+sep+PBKDF2_HMAC_SHA256(CryptProtectDataEntropy,User)
// with sep='_' under Windows, and sep='.syn-' under Linux/Posix
// - under Windows, it will encode the secret file via CryptProtectData DPAPI,
// so has the same security level than plain CryptDataForCurrentUserDPAPI()
// - under Linux/POSIX, access to the $HOME user's .xxxxxxxxxxx secret file with
// chmod 400 is considered to be a safe enough approach
// - this function is up to 100 times faster than CryptDataForCurrentUserDPAPI,
// generates smaller results, and is consistent on all Operating Systems
// - you can use this function over a specified variable, to cypher it in place,
// with try ... finally block to protect memory access of the plain data:
// ! constructor TMyClass.Create;
// ! ...
// ! fSecret := CryptDataForCurrentUser('Some Secret Value','appsalt',true);
// ! ...
// ! procedure TMyClass.DoSomething;
// ! var plain: RawByteString;
// ! begin
// ! plain := CryptDataForCurrentUser(fSecret,'appsalt',false);
// ! try
// ! // here plain = 'Some Secret Value'
// ! finally
// ! FillZero(plain); // safely erase uncyphered content from heap
// ! end;
// ! end;
function CryptDataForCurrentUser(const Data,AppSecret: RawByteString; Encrypt: boolean): RawByteString;
const
SHA1DIGESTSTRLEN = sizeof(TSHA1Digest)*2;
SHA256DIGESTSTRLEN = sizeof(TSHA256Digest)*2;
MD5DIGESTSTRLEN = sizeof(TMD5Digest)*2;
type
/// 32-characters ASCII string, e.g. as returned by AESBlockToShortString()
Short32 = string[32];
/// compute the hexadecial representation of an AES 16-byte block
// - returns a stack-allocated short string
function AESBlockToShortString(const block: TAESBlock): short32; overload;
{$ifdef HASINLINE}inline;{$endif}
/// compute the hexadecial representation of an AES 16-byte block
// - fill a stack-allocated short string
procedure AESBlockToShortString(const block: TAESBlock; out result: short32); overload;
{$ifdef HASINLINE}inline;{$endif}
/// compute the hexadecial representation of an AES 16-byte block
function AESBlockToString(const block: TAESBlock): RawUTF8;
/// compute the hexadecimal representation of a SHA-1 digest
function SHA1DigestToString(const D: TSHA1Digest): RawUTF8;
{$ifdef HASINLINE}inline;{$endif}
/// compute the SHA-1 digest from its hexadecimal representation
// - returns true on success (i.e. Source has the expected size and characters)
// - just a wrapper around SynCommons.HexToBin()
function SHA1StringToDigest(const Source: RawUTF8; out Dest: TSHA1Digest): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// compute the hexadecimal representation of a SHA-256 digest
function SHA256DigestToString(const D: TSHA256Digest): RawUTF8;
{$ifdef HASINLINE}inline;{$endif}
/// compute the SHA-256 digest from its hexadecimal representation
// - returns true on success (i.e. Source has the expected size and characters)
// - just a wrapper around SynCommons.HexToBin()
function SHA256StringToDigest(const Source: RawUTF8; out Dest: TSHA256Digest): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// compute the hexadecimal representation of a SHA-384 digest
function SHA384DigestToString(const D: TSHA384Digest): RawUTF8;
{$ifdef HASINLINE}inline;{$endif}
/// compute the hexadecimal representation of a SHA-512 digest
function SHA512DigestToString(const D: TSHA512Digest): RawUTF8;
{$ifdef HASINLINE}inline;{$endif}
/// compute the hexadecimal representation of a MD5 digest
function MD5DigestToString(const D: TMD5Digest): RawUTF8;
{$ifdef HASINLINE}inline;{$endif}
/// compute the MD5 digest from its hexadecimal representation
// - returns true on success (i.e. Source has the expected size and characters)
// - just a wrapper around SynCommons.HexToBin()
function MD5StringToDigest(const Source: RawUTF8; out Dest: TMD5Digest): boolean;
/// apply the XOR operation to the supplied binary buffers of 16 bytes
procedure XorBlock16(A,B: {$ifdef CPU64}PInt64Array{$else}PCardinalArray{$endif});
{$ifdef HASINLINE}inline;{$endif} overload;
/// apply the XOR operation to the supplied binary buffers of 16 bytes
procedure XorBlock16(A,B,C: {$ifdef CPU64}PInt64Array{$else}PCardinalArray{$endif});
{$ifdef HASINLINE}inline;{$endif} overload;
/// compute the HTDigest for a user and a realm, according to a supplied password
// - apache-compatible: 'agent007:download area:8364d0044ef57b3defcfa141e8f77b65'
function htdigest(const user, realm, pass: RawByteString): RawUTF8;
/// self test of Adler32 routines
function Adler32SelfTest: boolean;
/// self test of MD5 routines
function MD5SelfTest: boolean;
/// self test of SHA-1 routines
function SHA1SelfTest: boolean;
/// self test of SHA-256 routines
function SHA256SelfTest: boolean;
/// self test of AES routines
function AESSelfTest(onlytables: Boolean): boolean;
/// self test of RC4 routines
function RC4SelfTest: boolean;
/// entry point of the MD5 transform function - may be used from outside
procedure RawMd5Compress(var Hash; Data: pointer);
/// entry point of the SHA-1 transform function - may be used from outside
procedure RawSha1Compress(var Hash; Data: pointer);
/// entry point of the SHA-256 transform function - may be used from outside
procedure RawSha256Compress(var Hash; Data: pointer);
/// entry point of the SHA-512 transform function - may be used from outside
procedure RawSha512Compress(var Hash; Data: pointer);
// little endian fast conversion
// - 160 bits = 5 integers
// - use fast bswap asm in x86/x64 mode
procedure bswap160(s,d: PIntegerArray);
// little endian fast conversion
// - 256 bits = 8 integers
// - use fast bswap asm in x86/x64 mode
procedure bswap256(s,d: PIntegerArray);
/// simple Adler32 implementation
// - a bit slower than Adler32Asm() version below, but shorter code size
function Adler32Pas(Adler: cardinal; p: pointer; Count: Integer): cardinal;
/// fast Adler32 implementation
// - 16-bytes-chunck unrolled asm version
function Adler32Asm(Adler: cardinal; p: pointer; Count: Integer): cardinal;
{$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif}
// - very fast XOR according to Cod - not Compression or Stream compatible
// - used in AESFull() for KeySize=32
procedure XorBlock(p: PIntegerArray; Count, Cod: integer);
/// fast and simple XOR Cypher using Index (=Position in Dest Stream)
// - Compression not compatible with this function: should be applied after
// compress (e.g. as outStream for TAESWriteStream)
// - Stream compatible (with updated Index)
// - used in AES() and TAESWriteStream
procedure XorOffset(P: PByteArray; Index,Count: integer);
/// fast XOR Cypher changing by Count value
// - Compression compatible, since the XOR value is always the same, the
// compression rate will not change a lot
procedure XorConst(p: PIntegerArray; Count: integer);
var
/// the encryption key used by CompressShaAes() global function
// - the key is global to the whole process
// - use CompressShaAesSetKey() procedure to set this Key from text
CompressShaAesKey: TSHA256Digest;
/// the AES-256 encoding class used by CompressShaAes() global function
// - use any of the implementation classes, corresponding to the chaining
// mode required - TAESECB, TAESCBC, TAESCFB, TAESOFB and TAESCTR classes to
// handle in ECB, CBC, CFB, OFB and CTR mode (including PKCS7-like padding)
// - set to the secure and efficient CFB mode by default
CompressShaAesClass: TAESAbstractClass = TAESCFB;
/// set an text-based encryption key for CompressShaAes() global function
// - will compute the key via SHA256Weak() and set CompressShaAesKey
// - the key is global to the whole process
procedure CompressShaAesSetKey(const Key: RawByteString; AesClass: TAESAbstractClass=nil);
/// encrypt data content using the AES-256/CFB algorithm, after SynLZ compression
// - as expected by THttpSocket.RegisterCompress()
// - will return 'synshaaes' as ACCEPT-ENCODING: header parameter
// - will use global CompressShaAesKey / CompressShaAesClass variables to be set
// according to the expected algorithm and Key e.g. via a call to CompressShaAesSetKey()
// - if you want to change the chaining mode, you can customize the global
// CompressShaAesClass variable to the expected TAES* class name
// - will store a hash of both cyphered and clear stream: if the
// data is corrupted during transmission, will instantly return ''
function CompressShaAes(var DataRawByteString; Compress: boolean): AnsiString;
type
/// possible return codes by IProtocol classes
TProtocolResult = (sprSuccess,
sprBadRequest, sprUnsupported, sprUnexpectedAlgorithm,
sprInvalidCertificate, sprInvalidSignature,
sprInvalidEphemeralKey, sprInvalidPublicKey, sprInvalidPrivateKey,
sprInvalidMAC);
/// perform safe communication after unilateral or mutual authentication
// - see e.g. TProtocolNone or SynEcc's TECDHEProtocolClient and
// TECDHEProtocolServer implementation classes
IProtocol = interface
['{91E3CA39-3AE2-44F4-9B8C-673AC37C1D1D}']
/// initialize the communication by exchanging some client/server information
// - expects the handshaking messages to be supplied as UTF-8 text, may be as
// base64-encoded binary - see e.g. TWebSocketProtocolBinary.ProcessHandshake
// - should return sprUnsupported if the implemented protocol does not
// expect any handshaking mechanism
// - returns sprSuccess and set something into OutData, depending on the
// current step of the handshake
// - returns an error code otherwise
function ProcessHandshake(const MsgIn: RawUTF8; out MsgOut: RawUTF8): TProtocolResult;
/// encrypt a message on one side, ready to be transmitted to the other side
// - this method should be thread-safe in the implementation class
procedure Encrypt(const aPlain: RawByteString; out aEncrypted: RawByteString);
/// decrypt a message on one side, as transmitted from the other side
// - should return sprSuccess if the
// - should return sprInvalidMAC in case of wrong aEncrypted input (e.g.
// packet corruption, MiM or Replay attacks attempts)
// - this method should be thread-safe in the implementation class
function Decrypt(const aEncrypted: RawByteString; out aPlain: RawByteString): TProtocolResult;
/// will create another instance of this communication protocol
function Clone: IProtocol;
end;
/// stores a list of IProtocol instances
IProtocolDynArray = array of IProtocol;
/// implements a fake no-encryption protocol
// - may be used for debugging purposes, or when encryption is not needed
TProtocolNone = class(TInterfacedObject, IProtocol)
public
/// initialize the communication by exchanging some client/server information
// - this method will return sprUnsupported
function ProcessHandshake(const MsgIn: RawUTF8; out MsgOut: RawUTF8): TProtocolResult;
/// encrypt a message on one side, ready to be transmitted to the other side
// - this method will return the plain text with no actual encryption
procedure Encrypt(const aPlain: RawByteString; out aEncrypted: RawByteString);
/// decrypt a message on one side, as transmitted from the other side
// - this method will return the encrypted text with no actual decryption
function Decrypt(const aEncrypted: RawByteString; out aPlain: RawByteString): TProtocolResult;
/// will create another instance of this communication protocol
function Clone: IProtocol;
end;
/// implements a secure protocol using AES encryption
// - as used e.g. by 'synopsebinary' WebSockets protocol
// - this class will maintain two TAESAbstract instances, one for encryption
// and another one for decryption, with PKCS7 padding and no MAC validation
TProtocolAES = class(TInterfacedObjectLocked, IProtocol)
protected
fAES: array[boolean] of TAESAbstract;
public
/// initialize this encryption protocol with the given AES settings
constructor Create(aClass: TAESAbstractClass; const aKey; aKeySize: cardinal;
aIVReplayAttackCheck: TAESIVReplayAttackCheck=repCheckedIfAvailable); reintroduce; virtual;
/// will create another instance of this communication protocol
constructor CreateFrom(aAnother: TProtocolAES); reintroduce; virtual;
/// finalize the encryption
destructor Destroy; override;
/// initialize the communication by exchanging some client/server information
// - this method will return sprUnsupported
function ProcessHandshake(const MsgIn: RawUTF8; out MsgOut: RawUTF8): TProtocolResult;
/// encrypt a message on one side, ready to be transmitted to the other side
// - this method uses AES encryption and PKCS7 padding
procedure Encrypt(const aPlain: RawByteString; out aEncrypted: RawByteString);
/// decrypt a message on one side, as transmitted from the other side
// - this method uses AES decryption and PKCS7 padding
function Decrypt(const aEncrypted: RawByteString; out aPlain: RawByteString): TProtocolResult;
/// will create another instance of this communication protocol
function Clone: IProtocol;
end;
/// class-reference type (metaclass) of an AES secure protocol
TProtocolAESClass = class of TProtocolAES;
{$ifndef NOVARIANTS}
type
/// JWT Registered Claims, as defined in RFC 7519
// - known registered claims have a specific name and behavior, and will be
// handled automatically by TJWTAbstract
// - corresponding field names are iss,sub,aud,exp,nbf,iat,jti - as defined
// in JWT_CLAIMS_TEXT constant
// - jrcIssuer identifies the server which originated the token, e.g.
// "iss":"https://example.auth0.com/" when the token comes from Auth0 servers
// - jrcSubject is the application-specific extent which is protected by this
// JWT, e.g. an User or Resource ID, e.g. "sub":"auth0|57fe9f1bad961aa242870e"
// - jrcAudience claims that the token is valid only for one or several
// resource servers (may be a JSON string or a JSON array of strings), e.g.
// "aud":["https://myshineyfileserver.sometld"] - TJWTAbstract will check
// that the supplied "aud" field does match an expected list of identifiers
// - jrcExpirationTime contains the Unix timestamp in seconds after which
// the token must not be granted access, e.g. "exp":1477474667
// - jrcNotBefore contains the Unix timestamp in seconds before which the
// token must not be granted access, e.g. "nbf":147745438
// - jrcIssuedAt contains the Unix timestamp in seconds when the token was
// generated, e.g. "iat":1477438667
// - jrcJwtID provides a unique identifier for the JWT, to prevent any replay;
// TJWTAbstract.Compute will set an obfuscated TSynUniqueIdentifierGenerator
// hexadecimal value
TJWTClaim = (
jrcIssuer, jrcSubject, jrcAudience, jrcExpirationTime, jrcNotBefore,
jrcIssuedAt, jrcJwtID);
/// set of JWT Registered Claims, as in TJWTAbstract.Claims
TJWTClaims = set of TJWTClaim;
/// Exception raised when running JSON Web Tokens
EJWTException = class(ESynException);
/// TJWTContent.result codes after TJWTAbstract.Verify method call
TJWTResult = (jwtValid,
jwtNoToken, jwtWrongFormat, jwtInvalidAlgorithm, jwtInvalidPayload,
jwtUnexpectedClaim, jwtMissingClaim, jwtUnknownAudience,
jwtExpired, jwtNotBeforeFailed, jwtInvalidIssuedAt, jwtInvalidID,
jwtInvalidSignature);
//// set of TJWTContent.result codes
TJWTResults = set of TJWTResult;
/// JWT decoded content, as processed by TJWTAbstract
// - optionally cached in memory
TJWTContent = record
/// store latest Verify() result
result: TJWTResult;
/// set of registered claims, as stored in the JWT payload
claims: TJWTClaims;
/// match TJWTAbstract.Audience[] indexes for reg[jrcAudience]
audience: set of 0..15;
/// registered claims UTF-8 values, as stored in the JWT payload
// - e.g. reg[jrcSubject]='1234567890' and reg[jrcIssuer]='' for
// $ {"sub": "1234567890","name": "John Doe","admin": true}
reg: array[TJWTClaim] of RawUTF8;
/// unregistered public/private claim values, as stored in the JWT payload
// - registered claims will be available from reg[], not in this field
// - e.g. data.U['name']='John Doe' and data.B['admin']=true for
// $ {"sub": "1234567890","name": "John Doe","admin": true}
// but data.U['sub'] if not defined, and reg[jrcSubject]='1234567890'
data: TDocVariantData;
end;
/// pointer to a JWT decoded content, as processed by TJWTAbstract
PJWTContent = ^TJWTContent;
/// used to store a list of JWT decoded content
// - as used e.g. by TJWTAbstract cache
TJWTContentDynArray = array of TJWTContent;
/// available options for TJWTAbstract process
TJWTOption = (joHeaderParse, joAllowUnexpectedClaims, joAllowUnexpectedAudience,
joNoJwtIDGenerate, joNoJwtIDCheck, joDoubleInData);
/// store options for TJWTAbstract process
TJWTOptions = set of TJWTOption;
/// abstract parent class for implementing JSON Web Tokens
// - to represent claims securely between two parties, as defined in industry
// standard @http://tools.ietf.org/html/rfc7519
// - you should never use this abstract class directly, but e.g. TJWTHS256,
// TJWTHS384, TJWTHS512 or TJWTES256 (as defined in SynEcc.pas) inherited classes
// - for security reasons, one inherited class is implementing a single
// algorithm, as is very likely to be the case on production: you pickup one
// "alg", then you stick to it; if your server needs more than one algorithm
// for compatibility reasons, use a separate key and URI - this design will
// reduce attack surface, and fully avoid weaknesses as described in
// @https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries
// and @http://tools.ietf.org/html/rfc7518#section-8.5
TJWTAbstract = class(TSynPersistent)
protected
fAlgorithm: RawUTF8;
fHeader: RawUTF8;
fHeaderB64: RawUTF8;
fClaims: TJWTClaims;
fOptions: TJWTOptions;
fAudience: TRawUTF8DynArray;
fExpirationSeconds: integer;
fIDGen: TSynUniqueIdentifierGenerator;
fCacheTimeoutSeconds: integer;
fCacheResults: TJWTResults;
fCache: TSynDictionary;
procedure SetCacheTimeoutSeconds(value: integer); virtual;
function PayloadToJSON(const DataNameValue: array of const;
const Issuer, Subject, Audience: RawUTF8; NotBefore: TDateTime;
ExpirationMinutes: cardinal): RawUTF8; virtual;
procedure Parse(const Token: RawUTF8; var JWT: TJWTContent;
out headpayload: RawUTF8; out signature: RawByteString; excluded: TJWTClaims); virtual;
function CheckAgainstActualTimestamp(var JWT: TJWTContent): boolean;
// abstract methods which should be overriden by inherited classes
function ComputeSignature(const headpayload: RawUTF8): RawUTF8; virtual; abstract;
procedure CheckSignature(const headpayload: RawUTF8; const signature: RawByteString;
var JWT: TJWTContent); virtual; abstract;
public
/// initialize the JWT processing instance
// - the supplied set of claims are expected to be defined in the JWT payload
// - aAudience are the allowed values for the jrcAudience claim
// - aExpirationMinutes is the deprecation time for the jrcExpirationTime claim
// - aIDIdentifier and aIDObfuscationKey are passed to a
// TSynUniqueIdentifierGenerator instance used for jrcJwtID claim
constructor Create(const aAlgorithm: RawUTF8; aClaims: TJWTClaims;
const aAudience: array of RawUTF8; aExpirationMinutes: integer;
aIDIdentifier: TSynUniqueIdentifierProcess; aIDObfuscationKey: RawUTF8); reintroduce;
/// finalize the instance
destructor Destroy; override;
/// compute a new JWT for a given payload
// - here the data payload is supplied as Name,Value pairs - by convention,
// some registered Names (see TJWTClaim) should not be used here, and private
// claims names are expected to be short (typically 3 chars), or an URI
// - depending on the instance Claims, you should also specify associated
// Issuer, Subject, Audience and NotBefore values; expected 'exp', 'nbf',
// 'iat', 'jti' claims will also be generated and included, if needed
// - you can override the aExpirationMinutes value as defined in Create()
// - Audience is usually a single text, serialized as a JSON string, but
// if the value supplied starts with '[', it is expected to be an array
// of text values, already serialized as a JSON array of strings
// - this method is thread-safe
function Compute(const DataNameValue: array of const; const Issuer: RawUTF8='';
const Subject: RawUTF8=''; const Audience: RawUTF8=''; NotBefore: TDateTime=0;
ExpirationMinutes: integer=0; Signature: PRawUTF8=nil): RawUTF8;
/// compute a HTTP Authorization header containing a JWT for a given payload
// - just a wrapper around Compute(), returned the HTTP header value:
// $ Authorization: <HttpAuthorizationHeader>
// following the expected pattern:
// $ Authorization: Bearer <Token>
// - this method is thread-safe
function ComputeAuthorizationHeader(const DataNameValue: array of const;
const Issuer: RawUTF8=''; const Subject: RawUTF8=''; const Audience: RawUTF8='';
NotBefore: TDateTime=0; ExpirationMinutes: integer=0): RawUTF8;
/// check a JWT value, and its signature
// - will validate all expected Claims (minus ExcludedClaims optional
// parameter), and the associated signature
// - verification state is returned in JWT.result (jwtValid for a valid JWT),
// together with all parsed payload information
// - supplied JWT is transmitted e.g. in HTTP header:
// $ Authorization: Bearer <Token>
// - this method is thread-safe
procedure Verify(const Token: RawUTF8; out JWT: TJWTContent;
ExcludedClaims: TJWTClaims=[]); overload;
/// check a JWT value, and its signature
// - will validate all expected Claims, and the associated signature
// - verification state is returned as function result
// - supplied JWT is transmitted e.g. in HTTP header:
// $ Authorization: Bearer <Token>
// - this method is thread-safe
function Verify(const Token: RawUTF8): TJWTResult; overload;
/// check a HTTP Authorization header value as JWT, and its signature
// - will validate all expected Claims, and the associated signature
// - verification state is returned in JWT.result (jwtValid for a valid JWT),
// together with all parsed payload information
// - expect supplied HttpAuthorizationHeader as transmitted in HTTP header:
// $ Authorization: <HttpAuthorizationHeader>
// - this method is thread-safe
function VerifyAuthorizationHeader(const HttpAuthorizationHeader: RawUTF8;
out JWT: TJWTContent): boolean; overload;
/// in-place decoding and quick check of the JWT paylod
// - it won't check the signature, but the header's algorithm against the
// class name (use TJWTAbstract class to allow any algorithm)
// - it will decode the JWT payload and check for its expiration, and some
// mandatory fied values - you can optionally retrieve the Expiration time,
// the ending Signature, and/or the Payload decoded as TDocVariant
// - NotBeforeDelta allows to define some time frame for the "nbf" field
// - may be used on client side to quickly validate a JWT received from
// server, without knowing the exact algorithm or secret keys
class function VerifyPayload(const Token, ExpectedSubject, ExpectedIssuer,
ExpectedAudience: RawUTF8; Expiration: PUnixTime=nil; Signature: PRawUTF8=nil;
Payload: PVariant=nil; IgnoreTime: boolean=false; NotBeforeDelta: TUnixTime=15): TJWTResult;
published
/// the name of the algorithm used by this instance (e.g. 'HS256')
property Algorithm: RawUTF8 read fAlgorithm;
/// allow to tune the Verify and Compute method process
property Options: TJWTOptions read fOptions write fOptions;
/// the JWT Registered Claims, as implemented by this instance
// - Verify() method will ensure all claims are defined in the payload,
// then fill TJWTContent.reg[] with all corresponding values
property Claims: TJWTClaims read fClaims;
/// the period, in seconds, for the "exp" claim
property ExpirationSeconds: integer read fExpirationSeconds;
/// the audience string values associated with this instance
// - will be checked by Verify() method, and set in TJWTContent.audience
property Audience: TRawUTF8DynArray read fAudience;
/// delay of optional in-memory cache of Verify() TJWTContent
// - equals 0 by default, i.e. cache is disabled
// - may be useful if the signature process is very resource consumming
// (e.g. for TJWTES256 or even HMAC-SHA-256) - see also CacheResults
// - each time this property is assigned, internal cache content is flushed
property CacheTimeoutSeconds: integer read fCacheTimeoutSeconds
write SetCacheTimeoutSeconds;
/// which TJWTContent.result should be stored in in-memory cache
// - default is [jwtValid] but you may also include jwtInvalidSignature
// if signature checking uses a lot of resources
// - only used if CacheTimeoutSeconds>0
property CacheResults: TJWTResults read fCacheResults write fCacheResults;
end;
/// class-reference type (metaclass) of a JWT algorithm process
TJWTAbstractClass = class of TJWTAbstract;
/// implements JSON Web Tokens using 'none' algorithm
// - as defined in @http://tools.ietf.org/html/rfc7518 paragraph 3.6
// - you should never use this weak algorithm in production, unless your
// communication is already secured by other means, and use JWT as cookies
TJWTNone = class(TJWTAbstract)
protected
function ComputeSignature(const headpayload: RawUTF8): RawUTF8; override;
procedure CheckSignature(const headpayload: RawUTF8; const signature: RawByteString;
var JWT: TJWTContent); override;
public
/// initialize the JWT processing using the 'none' algorithm
// - the supplied set of claims are expected to be defined in the JWT payload
// - aAudience are the allowed values for the jrcAudience claim
// - aExpirationMinutes is the deprecation time for the jrcExpirationTime claim
// - aIDIdentifier and aIDObfuscationKey are passed to a
// TSynUniqueIdentifierGenerator instance used for jrcJwtID claim
constructor Create(aClaims: TJWTClaims; const aAudience: array of RawUTF8;
aExpirationMinutes: integer=0; aIDIdentifier: TSynUniqueIdentifierProcess=0;
aIDObfuscationKey: RawUTF8=''); reintroduce;
end;
/// abstract parent of JSON Web Tokens using HMAC-SHA2 or SHA-3 algorithms
// - SHA-3 is not yet officially defined in @http://tools.ietf.org/html/rfc7518
// but could be used as a safer (and sometimes faster) alternative to HMAC-SHA2
// - digital signature will be processed by an internal TSynSigner instance
// - never use this abstract class, but any inherited class, or
// JWT_CLASS[].Create to instantiate a JWT process from a given algorithm
TJWTSynSignerAbstract = class(TJWTAbstract)
protected
fSignPrepared: TSynSigner;
function GetAlgo: TSignAlgo; virtual; abstract;
function ComputeSignature(const headpayload: RawUTF8): RawUTF8; override;
procedure CheckSignature(const headpayload: RawUTF8; const signature: RawByteString;
var JWT: TJWTContent); override;
public
/// initialize the JWT processing using SHA3 algorithm
// - the supplied set of claims are expected to be defined in the JWT payload
// - the supplied secret text will be used to compute the digital signature,
// directly if aSecretPBKDF2Rounds=0, or via PBKDF2 iterative key derivation
// if some number of rounds were specified
// - aAudience are the allowed values for the jrcAudience claim
// - aExpirationMinutes is the deprecation time for the jrcExpirationTime claim
// - aIDIdentifier and aIDObfuscationKey are passed to a
// TSynUniqueIdentifierGenerator instance used for jrcJwtID claim
// - optionally return the PBKDF2 derivated key for aSecretPBKDF2Rounds>0
constructor Create(const aSecret: RawUTF8; aSecretPBKDF2Rounds: integer;
aClaims: TJWTClaims; const aAudience: array of RawUTF8; aExpirationMinutes: integer=0;
aIDIdentifier: TSynUniqueIdentifierProcess=0; aIDObfuscationKey: RawUTF8='';
aPBKDF2Secret: PHash512Rec=nil); reintroduce;
/// finalize the instance
destructor Destroy; override;
/// the digital signature size, in byte
property SignatureSize: integer read fSignPrepared.fSignatureSize;
/// the TSynSigner raw algorithm used for digital signature
property SignatureAlgo: TSignAlgo read fSignPrepared.fAlgo;
/// low-level read access to the internal signature structure
property SignPrepared: TSynSigner read fSignPrepared;
end;
/// meta-class for TJWTSynSignerAbstract creations
TJWTSynSignerAbstractClass = class of TJWTSynSignerAbstract;
/// implements JSON Web Tokens using 'HS256' (HMAC SHA-256) algorithm
// - as defined in @http://tools.ietf.org/html/rfc7518 paragraph 3.2
// - our HMAC SHA-256 implementation used is thread safe, and very fast
// (x86: 3us, x64: 2.5us) so cache is not needed
// - resulting signature size will be of 256 bits
TJWTHS256 = class(TJWTSynSignerAbstract)
protected
function GetAlgo: TSignAlgo; override;
end;
/// implements JSON Web Tokens using 'HS384' (HMAC SHA-384) algorithm
// - as defined in @http://tools.ietf.org/html/rfc7518 paragraph 3.2
// - our HMAC SHA-384 implementation used is thread safe, and very fast
// even on x86 (if the CPU supports SSE3 opcodes)
// - resulting signature size will be of 384 bits
TJWTHS384 = class(TJWTSynSignerAbstract)
protected
function GetAlgo: TSignAlgo; override;
end;
/// implements JSON Web Tokens using 'HS512' (HMAC SHA-512) algorithm
// - as defined in @http://tools.ietf.org/html/rfc7518 paragraph 3.2
// - our HMAC SHA-512 implementation used is thread safe, and very fast
// even on x86 (if the CPU supports SSE3 opcodes)
// - resulting signature size will be of 512 bits
TJWTHS512 = class(TJWTSynSignerAbstract)
protected
function GetAlgo: TSignAlgo; override;
end;
/// experimental JSON Web Tokens using SHA3-224 algorithm
// - SHA-3 is not yet officially defined in @http://tools.ietf.org/html/rfc7518
// but could be used as a safer (and sometimes faster) alternative to HMAC-SHA2
// - resulting signature size will be of 224 bits
TJWTS3224 = class(TJWTSynSignerAbstract)
protected
function GetAlgo: TSignAlgo; override;
end;
/// experimental JSON Web Tokens using SHA3-256 algorithm
// - SHA-3 is not yet officially defined in @http://tools.ietf.org/html/rfc7518
// but could be used as a safer (and sometimes faster) alternative to HMAC-SHA2
// - resulting signature size will be of 256 bits
TJWTS3256 = class(TJWTSynSignerAbstract)
protected
function GetAlgo: TSignAlgo; override;
end;
/// experimental JSON Web Tokens using SHA3-384 algorithm
// - SHA-3 is not yet officially defined in @http://tools.ietf.org/html/rfc7518
// but could be used as a safer (and sometimes faster) alternative to HMAC-SHA2
// - resulting signature size will be of 384 bits
TJWTS3384 = class(TJWTSynSignerAbstract)
protected
function GetAlgo: TSignAlgo; override;
end;
/// experimental JSON Web Tokens using SHA3-512 algorithm
// - SHA-3 is not yet officially defined in @http://tools.ietf.org/html/rfc7518
// but could be used as a safer (and sometimes faster) alternative to HMAC-SHA2
// - resulting signature size will be of 512 bits
TJWTS3512 = class(TJWTSynSignerAbstract)
protected
function GetAlgo: TSignAlgo; override;
end;
/// experimental JSON Web Tokens using SHA3-SHAKE128 algorithm
// - SHA-3 is not yet officially defined in @http://tools.ietf.org/html/rfc7518
// but could be used as a safer (and sometimes faster) alternative to HMAC-SHA2
// - resulting signature size will be of 256 bits
TJWTS3S128 = class(TJWTSynSignerAbstract)
protected
function GetAlgo: TSignAlgo; override;
end;
/// experimental JSON Web Tokens using SHA3-SHAKE256 algorithm
// - SHA-3 is not yet officially defined in @http://tools.ietf.org/html/rfc7518
// but could be used as a safer (and sometimes faster) alternative to HMAC-SHA2
// - resulting signature size will be of 512 bits
TJWTS3S256 = class(TJWTSynSignerAbstract)
protected
function GetAlgo: TSignAlgo; override;
end;
const
/// the text field names of the registerd claims, as defined by RFC 7519
// - see TJWTClaim enumeration and TJWTClaims set
// - RFC standard expects those to be case-sensitive
JWT_CLAIMS_TEXT: array[TJWTClaim] of RawUTF8 = (
'iss','sub','aud','exp','nbf','iat','jti');
/// how TJWTSynSignerAbstract algorithms are identified in the JWT
// - SHA-1 will fallback to HS256 (since there will never be SHA-1 support)
// - SHA-3 is not yet officially defined in @http://tools.ietf.org/html/rfc7518
JWT_TEXT: array[TSignAlgo] of RawUTF8 = (
'HS256','HS256','HS384','HS512','S3224','S3256','S3384','S3512','S3S128','S3S256');
/// able to instantiate any of the TJWTSynSignerAbstract instance expected
// - SHA-1 will fallback to TJWTHS256 (since there will never be SHA-1 support)
// - SHA-3 is not yet officially defined in @http://tools.ietf.org/html/rfc7518
// - typical use is the following:
// ! result := JWT_CLASS[algo].Create(master, round, claims, [], expirationMinutes);
JWT_CLASS: array[TSignAlgo] of TJWTSynSignerAbstractClass = (
TJWTHS256, TJWTHS256, TJWTHS384, TJWTHS512,
TJWTS3224, TJWTS3256, TJWTS3384, TJWTS3512, TJWTS3S128, TJWTS3S256);
function ToText(res: TJWTResult): PShortString; overload;
function ToCaption(res: TJWTResult): string; overload;
function ToText(claim: TJWTClaim): PShortString; overload;
function ToText(claims: TJWTClaims): ShortString; overload;
{$endif NOVARIANTS}
function ToText(chk: TAESIVReplayAttackCheck): PShortString; overload;
function ToText(res: TProtocolResult): PShortString; overload;
function ToText(algo: TSignAlgo): PShortString; overload;
function ToText(algo: THashAlgo): PShortString; overload;
function ToText(algo: TSHA3Algo): PShortString; overload;
implementation
{$ifndef NOVARIANTS}
uses
Variants;
{$endif}
function ToText(res: TProtocolResult): PShortString;
begin
result := GetEnumName(TypeInfo(TProtocolResult),ord(res));
end;
function ToText(chk: TAESIVReplayAttackCheck): PShortString;
begin
result := GetEnumName(TypeInfo(TAESIVReplayAttackCheck),ord(chk));
end;
function ToText(algo: TSignAlgo): PShortString;
begin
result := GetEnumName(TypeInfo(TSignAlgo),ord(algo));
end;
function ToText(algo: THashAlgo): PShortString;
begin
result := GetEnumName(TypeInfo(THashAlgo),ord(algo));
end;
function ToText(algo: TSHA3Algo): PShortString;
begin
result := GetEnumName(TypeInfo(TSHA3Algo),ord(algo));
end;
{$ifdef CPU64}
procedure XorBlock16(A,B: PInt64Array);
begin
A[0] := A[0] xor B[0];
A[1] := A[1] xor B[1];
end;
procedure XorBlock16(A,B,C: PInt64Array);
begin
B[0] := A[0] xor C[0];
B[1] := A[1] xor C[1];
end;
{$else}
procedure XorBlock16(A,B: PCardinalArray);
begin
A[0] := A[0] xor B[0];
A[1] := A[1] xor B[1];
A[2] := A[2] xor B[2];
A[3] := A[3] xor B[3];
end;
procedure XorBlock16(A,B,C: PCardinalArray);
begin
B[0] := A[0] xor C[0];
B[1] := A[1] xor C[1];
B[2] := A[2] xor C[2];
B[3] := A[3] xor C[3];
end;
{$endif}
{$ifdef USEPADLOCK}
const
AES_SUCCEEDED = 0;
KEY_128BITS = 0;
KEY_192BITS = 1;
KEY_256BITS = 2;
ACE_AES_ECB = 0;
ACE_AES_CBC = 1;
{$ifdef USEPADLOCKDLL}
type
tpadlock_phe_available = function: boolean; cdecl;
tpadlock_phe_sha = function(
buffer: pointer; nbytes: integer; var Digest): integer; cdecl;
tpadlock_ace_available = function: boolean; cdecl;
tpadlock_aes_begin = function: pointer; cdecl;
tpadlock_aes_setkey = function(
ctx: pointer; const key; key_len: integer): integer; cdecl;
tpadlock_aes_setmodeiv = function(
ctx: pointer; mode: integer; var iv): integer; cdecl;
tpadlock_aes_encrypt = function(
ctx, bIn, bOut: pointer; nbytes: integer): integer; cdecl;
tpadlock_aes_decrypt = function(
ctx, bIn, bOut: pointer; nbytes: integer): integer; cdecl;
tpadlock_aes_close = function(
ctx: pointer): integer; cdecl;
var
padlock_phe_available: tpadlock_phe_available = nil;
padlock_phe_sha1: tpadlock_phe_sha = nil;
padlock_phe_sha256: tpadlock_phe_sha = nil;
padlock_ace_available: tpadlock_ace_available = nil;
padlock_aes_begin: tpadlock_aes_begin = nil;
padlock_aes_setkey: tpadlock_aes_setkey = nil;
padlock_aes_setmodeiv: tpadlock_aes_setmodeiv = nil;
padlock_aes_encrypt: tpadlock_aes_encrypt = nil;
padlock_aes_decrypt: tpadlock_aes_decrypt = nil;
padlock_aes_close: tpadlock_aes_close = nil;
{$ifdef MSWINDOWS}
PadLockLibHandle: THandle = 0;
{$else} // Linux:
PadLockLibHandle: HMODULE = 0;
{$endif}
procedure PadlockInit;
begin
{$ifdef MSWINDOWS}
PadLockLibHandle := LoadLibrary('LibPadlock');
{$else} // Linux:
PadLockLibHandle := LoadLibrary('libvia_padlock.so');
if PadLockLibHandle=0 then
PadLockLibHandle := LoadLibrary('libvia_padlock.so.1.0.0');
{$endif}
if PadLockLibHandle=0 then
exit;
padlock_phe_available := GetProcAddress(PadLockLibHandle,'padlock_phe_available');
padlock_phe_sha1 := GetProcAddress(PadLockLibHandle,'padlock_phe_sha1');
padlock_phe_sha256 := GetProcAddress(PadLockLibHandle,'padlock_phe_sha256');
padlock_ace_available := GetProcAddress(PadLockLibHandle,'padlock_ace_available');
padlock_aes_begin := GetProcAddress(PadLockLibHandle,'padlock_aes_begin');
padlock_aes_setkey := GetProcAddress(PadLockLibHandle,'padlock_aes_setkey');
padlock_aes_setmodeiv := GetProcAddress(PadLockLibHandle,'padlock_aes_setmodeiv');
padlock_aes_encrypt := GetProcAddress(PadLockLibHandle,'padlock_aes_encrypt');
padlock_aes_decrypt := GetProcAddress(PadLockLibHandle,'padlock_aes_decrypt');
padlock_aes_close := GetProcAddress(PadLockLibHandle,'padlock_aes_close');
if @padlock_phe_available=nil then exit;
if @padlock_phe_sha1=nil then exit;
if @padlock_phe_sha256=nil then exit;
if @padlock_ace_available=nil then exit;
if @padlock_aes_begin=nil then exit;
if @padlock_aes_setkey=nil then exit;
if @padlock_aes_setmodeiv=nil then exit;
if @padlock_aes_encrypt=nil then exit;
if @padlock_aes_decrypt=nil then exit;
if @padlock_aes_close=nil then exit;
if padlock_phe_available and padlock_ace_available then
padlock_available := true;
end;
{$else} // not USEPADLOCKDLL:
{$ifdef MSWINDOWS}
{$L padlock.obj}
{$L padlock_sha.obj}
{$L padlock_aes.obj}
{$else}
{$L padlock.o}
{$L padlock_sha.o}
{$L padlock_aes.o}
{$endif}
function memcpy(dest, src: Pointer; count: integer): Pointer; cdecl;
begin
MoveFast(src^, dest^, count);
Result := dest;
end;
function memset(dest: Pointer; val: Integer; count: integer): Pointer; cdecl;
begin
FillcharFast(dest^, count, val);
Result := dest;
end;
function malloc(size: integer): Pointer; cdecl;
begin
GetMem(Result, size);
end;
procedure free(pBlock: Pointer); cdecl;
begin
FreeMem(pBlock);
end;
function printf(format:PAnsiChar; args:array of const): PAnsiChar; cdecl;
begin
result := format;
// called on error -> do nothing
end;
{ this .o files have been generated from the sdk sources with
gcc-2.95 -c -O2 padlock*.c -I../include
}
function padlock_phe_available: boolean; cdecl; external;
function padlock_phe_sha1(buf: pointer; nbytes: integer; var Digest): integer; cdecl; external;
function padlock_phe_sha256(buf: pointer; nbytes: integer; var Digest): integer; cdecl; external;
function padlock_ace_available: boolean; cdecl; external;
function padlock_aes_begin: pointer; cdecl; external;
function padlock_aes_setkey(ctx: pointer; const key; key_len: integer): integer; cdecl; external;
function padlock_aes_setmodeiv(ctx: pointer; mode: integer; var iv): integer; cdecl; external;
function padlock_aes_encrypt(ctx, bIn, bOut: pointer; nbytes: integer): integer; cdecl; external;
function padlock_aes_decrypt(ctx, bIn, bOut: pointer; nbytes: integer): integer; cdecl; external;
function padlock_aes_close(ctx: pointer): integer; cdecl; external;
procedure PadlockInit;
begin
if padlock_phe_available and padlock_ace_available then
padlock_available := true;
{$ifdef PADLOCKDEBUG}if padlock_available then writeln('PADLOCK available'); {$endif}
end;
{$endif USEPADLOCKDLL}
{$endif USEPADLOCK}
procedure XorMemoryPtrInt(dest, source: PPtrIntArray; count: integer);
{$ifdef HASINLINE}inline;{$endif}
{$ifdef FPC}
begin
while count>0 do begin
dec(count);
PPtrInt(dest)^ := PPtrInt(dest)^ xor PPtrInt(source)^;
inc(PPtrInt(dest));
inc(PPtrInt(source));
end;
end;
{$else}
var i: integer;
begin
for i := 0 to count-1 do
dest^[i] := dest^[i] xor source^[i];
end;
{$endif}
const
AESMaxRounds = 14;
type
TKeyArray = packed array[0..AESMaxRounds] of TAESBlock;
TAESContext = packed record
// don't change the structure below: it is fixed in the asm code
// -> use PUREPASCAL if you really have to change it
RK: TKeyArray; // Key (encr. or decr.)
IV: TAESBlock; // IV or CTR
buf: TAESBlock; // Work buffer
{$ifdef USEPADLOCK}
ViaCtx: pointer; // padlock_*() context
{$endif}
DoBlock: procedure(const ctxt, source, dest);
{$ifdef USEAESNI32}AesNi32: pointer;{$endif}
Initialized: boolean;
Rounds: byte; // Number of rounds
KeyBits: word; // Number of bits in key (128/192/256)
end;
// helper types for better code generation
type
TWA4 = TBlock128; // AES block as array of cardinal
TAWk = packed array[0..4*(AESMaxRounds+1)-1] of cardinal; // Key as array of cardinal
PWA4 = ^TWA4;
PAWk = ^TAWk;
const
RCon: array[0..9] of cardinal = ($01,$02,$04,$08,$10,$20,$40,$80,$1b,$36);
// AES computed tables - don't change the order below!
var
Td0, Td1, Td2, Td3, Te0, Te1, Te2, Te3: array[byte] of cardinal;
SBox, InvSBox: array[byte] of byte;
Xor32Byte: TByteArray absolute Td0; // 2^13=$2000=8192 bytes of XOR tables ;)
procedure ComputeAesStaticTables;
var i, x,y: byte;
pow,log: array[byte] of byte;
c: cardinal;
begin // 835 bytes of code to compute 4.5 KB of tables
x := 1;
for i := 0 to 255 do begin
pow[i] := x;
log[x] := i;
if x and $80<>0 then
x := x xor (x shl 1) xor $1B else
x := x xor (x shl 1);
end;
SBox[0] := $63;
InvSBox[$63] := 0;
for i := 1 to 255 do begin
x := pow[255-log[i]]; y := (x shl 1)+(x shr 7);
x := x xor y; y := (y shl 1)+(y shr 7);
x := x xor y; y := (y shl 1)+(y shr 7);
x := x xor y; y := (y shl 1)+(y shr 7);
x := x xor y xor $63;
SBox[i] := x;
InvSBox[x] := i;
end;
for i := 0 to 255 do begin
x := SBox[i];
y := x shl 1;
if x and $80<>0 then
y := y xor $1B;
Te0[i] := y+x shl 8+x shl 16+(y xor x)shl 24;
Te1[i] := Te0[i] shl 8+Te0[i] shr 24;
Te2[i] := Te1[i] shl 8+Te1[i] shr 24;
Te3[i] := Te2[i] shl 8+Te2[i] shr 24;
x := InvSBox[i];
if x=0 then
continue;
c := log[x]; // Td0[c] = Si[c].[0e,09,0d,0b] -> e.g. log[$0e]=223 below
Td0[i] := pow[(c+223)mod 255]+pow[(c+199)mod 255]shl 8+
pow[(c+238)mod 255]shl 16+pow[(c+104)mod 255]shl 24;
Td1[i] := Td0[i] shl 8+Td0[i] shr 24;
Td2[i] := Td1[i] shl 8+Td1[i] shr 24;
Td3[i] := Td2[i] shl 8+Td2[i] shr 24;
end;
end;
type
TSHAHash = packed record
A,B,C,D,E,F,G,H: cardinal; // will use A..E with TSHA1, A..H with TSHA256
end;
TSHAContext = packed record
// Working hash (TSHA256.Init expect this field to be the first)
Hash: TSHAHash;
// 64bit msg length
MLen: QWord;
// Block buffer
Buffer: array[0..63] of byte;
// Index in buffer
Index : integer;
end;
{$ifdef CPUINTEL}
{$ifdef CPU32}
procedure bswap256(s,d: PIntegerArray);
asm
push ebx
mov ecx,eax // ecx=s, edx=d
mov eax,[ecx]; mov ebx,[ecx+4]; bswap eax; bswap ebx; mov [edx],eax; mov [edx+4],ebx
mov eax,[ecx+8]; mov ebx,[ecx+12]; bswap eax; bswap ebx; mov [edx+8],eax; mov [edx+12],ebx
mov eax,[ecx+16]; mov ebx,[ecx+20]; bswap eax; bswap ebx; mov [edx+16],eax; mov [edx+20],ebx
mov eax,[ecx+24]; mov ebx,[ecx+28]; bswap eax; bswap ebx; mov [edx+24],eax; mov [edx+28],ebx
pop ebx
end;
procedure bswap160(s,d: PIntegerArray);
asm
push ebx
mov ecx,eax // ecx=s, edx=d
mov eax,[ecx]; mov ebx,[ecx+4]; bswap eax; bswap ebx; mov [edx],eax; mov [edx+4],ebx
mov eax,[ecx+8]; mov ebx,[ecx+12]; bswap eax; bswap ebx; mov [edx+8],eax; mov [edx+12],ebx
mov eax,[ecx+16]; bswap eax; mov [edx+16],eax
pop ebx
end;
{$endif CPU32}
{$ifdef CPU64}
procedure bswap256(s,d: PIntegerArray);
{$ifdef FPC}nostackframe; assembler; asm{$else}
asm // rcx=s, rdx=d
.noframe
{$endif}
{$ifndef win64}
mov rdx,rsi
mov rcx,rdi
{$endif win64}
mov eax,[rcx]; mov r8d,[rcx+4]; mov r9d,[rcx+8]; mov r10d,[rcx+12]
bswap eax; bswap r8d; bswap r9d; bswap r10d
mov [rdx],eax; mov [rdx+4],r8d; mov [rdx+8],r9d; mov [rdx+12],r10d
mov eax,[rcx+16]; mov r8d,[rcx+20]; mov r9d,[rcx+24]; mov r10d,[rcx+28]
bswap eax; bswap r8d; bswap r9d; bswap r10d
mov [rdx+16],eax; mov [rdx+20],r8d; mov [rdx+24],r9d; mov [rdx+28],r10d
end;
procedure bswap160(s,d: PIntegerArray);
{$ifdef FPC}nostackframe; assembler; asm{$else}
asm // rcx=s, rdx=d
.noframe
{$endif}
{$ifndef win64}
mov rdx,rsi
mov rcx,rdi
{$endif win64}
mov eax,[rcx]; mov r8d,[rcx+4]; mov r9d,[rcx+8]; mov r10d,[rcx+12];
bswap eax; bswap r8d; bswap r9d; bswap r10d;
mov [rdx],eax; mov [rdx+4],r8d; mov [rdx+8],r9d; mov [rdx+12],r10d;
mov eax,[rcx+16]; bswap eax; mov [rdx+16],eax
end;
{$endif CPU64}
{$else not CPUINTEL}
procedure bswap256(s,d: PIntegerArray);
begin
{$ifdef FPC} // use fast platform-specific function
d[0] := SwapEndian(s[0]);
d[1] := SwapEndian(s[1]);
d[2] := SwapEndian(s[2]);
d[3] := SwapEndian(s[3]);
d[4] := SwapEndian(s[4]);
d[5] := SwapEndian(s[5]);
d[6] := SwapEndian(s[6]);
d[7] := SwapEndian(s[7]);
{$else}
d[0] := bswap32(s[0]);
d[1] := bswap32(s[1]);
d[2] := bswap32(s[2]);
d[3] := bswap32(s[3]);
d[4] := bswap32(s[4]);
d[5] := bswap32(s[5]);
d[6] := bswap32(s[6]);
d[7] := bswap32(s[7]);
{$endif FPC}
end;
procedure bswap160(s,d: PIntegerArray);
begin
{$ifdef FPC} // use fast platform-specific function
d[0] := SwapEndian(s[0]);
d[1] := SwapEndian(s[1]);
d[2] := SwapEndian(s[2]);
d[3] := SwapEndian(s[3]);
d[4] := SwapEndian(s[4]);
{$else}
d[0] := bswap32(s[0]);
d[1] := bswap32(s[1]);
d[2] := bswap32(s[2]);
d[3] := bswap32(s[3]);
d[4] := bswap32(s[4]);
{$endif FPC}
end;
{$endif CPUINTEL}
function SHA256SelfTest: boolean;
function SingleTest(const s: RawByteString; const TDig: TSHA256Digest): boolean;
var SHA: TSHA256;
Digest: TSHA256Digest;
i: integer;
begin
// 1. Hash complete RawByteString
SHA.Full(pointer(s),length(s),Digest);
result := IsEqual(Digest,TDig);
if not result then exit;
// 2. one update call for all chars
SHA.Init;
for i := 1 to length(s) do
SHA.Update(@s[i],1);
SHA.Final(Digest);
result := IsEqual(Digest,TDig);
// 3. test consistency with Padlock engine down results
{$ifdef USEPADLOCK}
if not result or not padlock_available then exit;
padlock_available := false; // force PadLock engine down
SHA.Full(pointer(s),length(s),Digest);
result := IsEqual(Digest,TDig);
{$ifdef PADLOCKDEBUG} write('=padlock '); {$endif}
padlock_available := true;
{$endif}
end;
var Digest: TSHA256Digest;
const
D1: TSHA256Digest = ($ba,$78,$16,$bf,$8f,$01,$cf,$ea,$41,$41,$40,$de,$5d,$ae,$22,$23,
$b0,$03,$61,$a3,$96,$17,$7a,$9c,$b4,$10,$ff,$61,$f2,$00,$15,$ad);
D2: TSHA256Digest = ($24,$8d,$6a,$61,$d2,$06,$38,$b8,$e5,$c0,$26,$93,$0c,$3e,$60,$39,
$a3,$3c,$e4,$59,$64,$ff,$21,$67,$f6,$ec,$ed,$d4,$19,$db,$06,$c1);
D3: TSHA256Digest =
($94,$E4,$A9,$D9,$05,$31,$23,$1D,$BE,$D8,$7E,$D2,$E4,$F3,$5E,$4A,
$0B,$F4,$B3,$BC,$CE,$EB,$17,$16,$D5,$77,$B1,$E0,$8B,$A9,$BA,$A3);
begin
// result := true; exit;
result := SingleTest('abc', D1) and
SingleTest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', D2);
if not result then exit;
SHA256Weak('lagrangehommage',Digest); // test with len=256>64
result := IsEqual(Digest,D3);
{$ifdef CPU64}
{$ifdef CPUINTEL}
if cfSSE41 in CpuFeatures then begin
Exclude(CpuFeatures,cfSSE41);
result := result and SingleTest('abc', D1) and
SingleTest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', D2);
Include(CpuFeatures,cfSSE41);
end;
{$endif}
{$endif}
end;
function MD5(const s: RawByteString): RawUTF8;
var MD5: TMD5;
D: TMD5Digest;
begin
MD5.Full(pointer(s),Length(s),D);
result := MD5DigestToString(D);
FillZero(D);
end;
function SHA1(const s: RawByteString): RawUTF8;
var SHA: TSHA1;
Digest: TSHA1Digest;
begin
SHA.Full(pointer(s),length(s),Digest);
result := SHA1DigestToString(Digest);
FillZero(Digest);
end;
function SHA384(const s: RawByteString): RawUTF8;
var SHA: TSHA384;
Digest: TSHA384Digest;
begin
SHA.Full(pointer(s),length(s),Digest);
result := SHA384DigestToString(Digest);
FillZero(Digest);
end;
function SHA512(const s: RawByteString): RawUTF8;
var SHA: TSHA512;
Digest: TSHA512Digest;
begin
SHA.Full(pointer(s),length(s),Digest);
result := SHA512DigestToString(Digest);
FillZero(Digest);
end;
{ THMAC_SHA1 }
procedure FillZero(var hash: TSHA1Digest); overload;
begin
FillCharFast(hash,sizeof(hash),0);
end;
procedure FillZero(var hash: TByte64);
begin
FillCharFast(hash,sizeof(hash),0);
end;
function IsEqual(const A,B: TSHA1Digest): boolean;
var a_: TIntegerArray absolute A;
b_: TIntegerArray absolute B;
begin // uses anti-forensic time constant "xor/or" pattern
result := ((a_[0] xor b_[0]) or (a_[1] xor b_[1]) or (a_[2] xor b_[2]) or
(a_[3] xor b_[3]) or (a_[4] xor b_[4]))=0;
end;
procedure THMAC_SHA1.Init(key: pointer; keylen: integer);
var i: integer;
k0,k0xorIpad: TByte64;
begin
FillZero(k0);
if keylen>sizeof(k0) then
sha.Full(key,keylen,PSHA1Digest(@k0)^) else
MoveFast(key^,k0,keylen);
for i := 0 to 15 do
k0xorIpad[i] := k0[i] xor $36363636;
for i := 0 to 15 do
step7data[i] := k0[i] xor $5c5c5c5c;
sha.Init;
sha.Update(@k0xorIpad,sizeof(k0xorIpad));
FillZero(k0);
FillZero(k0xorIpad);
end;
procedure THMAC_SHA1.Update(msg: pointer; msglen: integer);
begin
sha.Update(msg,msglen);
end;
procedure THMAC_SHA1.Done(out result: TSHA1Digest; NoInit: boolean);
begin
sha.Final(result);
sha.Update(@step7data,sizeof(step7data));
sha.Update(@result,sizeof(result));
sha.Final(result,NoInit);
if not NoInit then
FillZero(step7data);
end;
procedure THMAC_SHA1.Done(out result: RawUTF8; NoInit: boolean);
var res: TSHA1Digest;
begin
Done(res,NoInit);
result := SHA1DigestToString(res);
if not NoInit then
FillZero(res);
end;
procedure THMAC_SHA1.Compute(msg: pointer; msglen: integer; out result: TSHA1Digest);
var temp: THMAC_SHA1;
begin
temp := self; // thread-safe copy
temp.Update(msg,msglen);
temp.Done(result);
end;
procedure HMAC_SHA1(key,msg: pointer; keylen,msglen: integer; out result: TSHA1Digest);
var mac: THMAC_SHA1;
begin
mac.Init(key,keylen);
mac.Update(msg,msglen);
mac.Done(result);
end;
procedure HMAC_SHA1(const key,msg: RawByteString; out result: TSHA1Digest);
begin
HMAC_SHA1(pointer(key),pointer(msg),length(key),length(msg),result);
end;
procedure HMAC_SHA1(const key: TSHA1Digest; const msg: RawByteString; out result: TSHA1Digest);
begin
HMAC_SHA1(@key,pointer(msg),sizeof(key),length(msg),result);
end;
procedure PBKDF2_HMAC_SHA1(const password,salt: RawByteString; count: Integer;
out result: TSHA1Digest);
var i: integer;
tmp: TSHA1Digest;
mac: THMAC_SHA1;
first: THMAC_SHA1;
begin
HMAC_SHA1(password,salt+#0#0#0#1,result);
if count<2 then
exit;
tmp := result;
first.Init(pointer(password),length(password));
for i := 2 to count do begin
mac := first; // re-use the very same SHA context for best performance
mac.sha.Update(@tmp,sizeof(tmp));
mac.Done(tmp,true);
XorMemory(@result,@tmp,sizeof(result));
end;
FillcharFast(mac,sizeof(mac),0);
FillcharFast(first,sizeof(first),0);