YOUSEFUL / DELPHI
Atlas Crypto: FAQ, examples and tutorial
Learning center · Component reference
Atlas Crypto gives Delphi programs hashing, message authentication, password and key derivation,
authenticated encryption of values, streams and files, and public-key signatures and key agreement, through a
small API that is hard to misuse. It implements no algorithm itself: every hash, cipher, signature and random
number comes from Windows CNG (bcrypt.dll), which the operating system maintains. Add Atlas.Crypto for byte
helpers, random numbers, SHA-2 hashes, HMAC, PBKDF2, HKDF and raw AES-GCM; Atlas.CryptoBox for sealed boxes
(TAtlasSecretBox) and chunked stream and file encryption (TAtlasStreamCrypt), the layer most applications
should use; Atlas.CryptoKeys for ECDSA and ECDH on P-256 and RSA with OAEP and PSS; and
Atlas.CryptoComponents for the two components on the Atlas Crypto palette page, TAtlasHashCalculator and
TAtlasFileProtector. It depends on no other Atlas product. The runtime package requires only the RTL, so it
works in services and console programs, on Windows 10 or later. The code has not yet been reviewed by a
cryptographer; the FAQ says what has and has not been checked.
FAQ
What does Atlas Crypto implement itself? No cryptographic algorithm. SHA-2, HMAC, PBKDF2, AES-GCM, ECDSA,
ECDH, RSA and the random generator are all Windows CNG calls. Atlas Crypto supplies the CNG declarations
(Atlas.Crypto.Cng, written from the public SDK documentation because Delphi ships none), a validating layer
over each primitive, three container formats that put the primitives together safely, and tests that check the
wiring against published test vectors. The library's own code is limited to plumbing around CNG: argument
checks, the containers, a constant-time comparison, rejection sampling for unbiased random ranges, and HKDF,
written as a few HMAC calls following RFC 5869 and checked against that RFC's test cases.
Which class should I use to encrypt data? TAtlasSecretBox for values that fit in memory, such as bytes,
text or a database field, sealed with a random key from NewKey or with a password. TAtlasStreamCrypt for
streams and files of any size, processed in 64 KB authenticated chunks. TAtlasFileProtector when you want a
component on a form: it wraps the password file and text functions and adds an Iterations property and an
OnProgress event. TAtlasAesGcm is the raw primitive. It takes a nonce from the caller because test vectors
need one, and using a nonce twice with the same key breaks GCM completely, so use it only to match an existing
format.
What happens when something does not open? Opening either returns exactly what was sealed or raises
EAtlasCryptoError; there is no way to get unauthenticated data out. A wrong key, a wrong password, altered,
truncated or extended data, a different context value and data that is not a box at all all raise the same
message, Decryption failed: wrong key or password, or the data was altered, so someone probing a service
learns nothing from the wording. Setup mistakes, such as a key that is not 32 bytes or an empty password when
sealing, raise their own specific messages. One case needs care: TAtlasStreamCrypt.Decrypt writes each chunk
to the destination stream as soon as that chunk is authenticated, so a stream that fails part-way leaves genuine
but incomplete output behind. DecryptFile avoids this by writing to <name>.part and deleting it on failure.
How are passwords protected, and why is sealing with one slow? A password is stretched with
PBKDF2-HMAC-SHA256, 600,000 rounds by default (TAtlasKdf.DefaultPasswordIterations), with a fresh 16-byte
random salt for every box. The slowness is the protection: each guess costs an attacker the same work. On the
machine used to write this page one seal or open at the default cost took about 0.2 seconds. The count is
stored in the box header and authenticated, so opening needs no setting, and a forged header asking for more
than 20,000,000 rounds (TAtlasSecretBox.MaxIterations) is refused before any work is done.
TAtlasFileProtector will not accept fewer than 10,000 rounds. There is no Argon2 or scrypt, because CNG offers
neither.
Can another program read what Atlas Crypto seals? Not without code written for it. ASB1 (key box),
ASP1 (password box) and ASF1 (stream) are this library's own containers, and their byte layout is described
at the top of Atlas.CryptoBox.pas. The primitives inside are standard (AES-256-GCM, PBKDF2-HMAC-SHA256), so
another implementation can be written from that description. No interoperability test against another
cryptography library has been done yet.
How do I store and exchange keys? Keys are CNG key blobs held in TBytes: TAtlasKeyPair.PublicKey and
PrivateKey. A private blob is as secret as a password, so keep it in protected storage (for example sealed with
TAtlasSecretBox) and call Wipe when you are done with it. To exchange an ECDSA public key with another
system, use TAtlasEcdsa.PublicKeyToXY and PublicKeyFromXY, which work with the two 32-byte curve
coordinates; a point that is not on the curve is rejected. ECDSA signatures are the raw 64-byte r then s
form (IEEE P1363), not DER. PEM and DER import and export (SPKI, PKCS#8) and X.509 certificates are not
implemented.
What is not covered? Not implemented yet: PEM/DER key files and X.509 certificates; DER-encoded ECDSA
signatures; ChaCha20-Poly1305, Ed25519 and X25519, the P-384 and P-521 curves, SHA-3, Argon2 and scrypt; keys
held in the Windows key store or a TPM (NCrypt) and DPAPI; JWT/JOSE, PKCS#7/CMS, PGP and TLS helpers. Left out
on purpose: ECB and unauthenticated CBC, PKCS#1 v1.5 encryption, raw RSA, and RSA keys shorter than 2048 bits.
The file functions take a password only; to encrypt a file with a key, call TAtlasStreamCrypt.Encrypt on two
file streams (see the examples).
Which platforms does it run on? Windows 10 or later, as Win32 and Win64 programs; both are built and tested.
There are no macOS, Linux, Android or iOS back ends and no FireMonkey demo. The public API contains no Windows
types, so a new back end would change only Atlas.Crypto.Cng and the code that calls it. FIPS mode, older
Windows versions and ARM64 have not been tested.
Is it thread safe? Nothing in Atlas Crypto keeps global state, so the class functions (TAtlasHash.Compute,
TAtlasSecretBox.Seal, TAtlasEcdsa.Sign and the rest) can be called from several threads at once. A
TAtlasHash object is one running computation, and the components hold their settings in properties, so give
each thread its own object.
Has the code been reviewed? Not by a cryptographer, and it should be before it protects anything valuable. The tests check the wiring against published known answers (FIPS 180 hashes, RFC 4231 HMAC, RFC 6070 and other PBKDF2 vectors, RFC 5869 HKDF, GCM specification cases 1, 2 and 4, and the RFC 6979 A.2.5 ECDSA signature), and check behaviour such as the detection of a flip in every single byte of a box. The AES-GCM and ECDSA expected values were not recomputed with a second implementation, RSA and ECDH are covered by round trips only, and constant-time behaviour relies on CNG and on the library's own comparison rather than on measurement. Memory hygiene is best effort: byte copies of keys and passwords are wiped, but Delphi strings holding passwords are not.
Why is the password a method parameter and not a property? A published property is written into the form
file, and from there into source control and the executable. TAtlasFileProtector therefore takes the password
in each call, and TAtlasHashCalculator has no secret at all. Keep passwords in memory for as short a time as
you can.
Are SHA-1 and MD5 safe? Not for security. Both are broken, and they are offered (hkSHA1, hkMD5) only for
checksums that are published with them and for talking to old systems. Signatures always hash with SHA-256, and
the password functions of TAtlasSecretBox, TAtlasStreamCrypt and TAtlasFileProtector always use
PBKDF2-HMAC-SHA256, whatever hash you pick elsewhere.
Examples
Hash text with SHA-256 and with the legacy SHA-1. Text is hashed as its UTF-8 bytes.
uses System.SysUtils, Atlas.Crypto;
procedure ShowDigests;
begin
WriteLn(TAtlasBytes.ToHex(TAtlasHash.OfString(hkSHA256, 'abc')));
WriteLn(TAtlasBytes.ToHex(TAtlasHash.OfString(hkSHA1, 'abc')));
end;Prints the FIPS 180 values, ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad and
a9993e364706816aba3e25717850c26c9cd0d89d.
Hash data that arrives in pieces. Update takes bytes or a buffer, and UpdateFromStream reads a stream from
its current position to its end.
uses System.SysUtils, System.Classes, Atlas.Crypto;
procedure HashInPieces;
var
Hash: TAtlasHash;
Rest: TBytesStream;
begin
Rest := TBytesStream.Create(TAtlasBytes.OfString('bc'));
Hash := TAtlasHash.Create(hkSHA256);
try
Hash.Update(TAtlasBytes.OfString('a'));
Hash.UpdateFromStream(Rest);
WriteLn(TAtlasBytes.ToHex(Hash.Finish));
finally
Hash.Free;
Rest.Free;
end;
end;Prints the same ba7816bf...f20015ad as hashing abc in one call. After Finish the object cannot be used
again.
Authenticate a message with a shared key (HMAC), and check a received tag in constant time.
uses System.SysUtils, Atlas.Crypto;
procedure CheckMessageTag;
var
Tag: TBytes;
begin
Tag := TAtlasHash.Hmac(hkSHA256, TAtlasBytes.OfString('Jefe'), TAtlasBytes.OfString('what do ya want for nothing?'));
WriteLn(TAtlasBytes.ToHex(Tag));
WriteLn(TAtlasBytes.ConstantTimeEquals(Tag,
TAtlasBytes.FromHex('5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843')));
end;Prints the RFC 4231 test case 2 value 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843, then
TRUE.
Stretch a password into a key with PBKDF2. Real code uses 16 random salt bytes per password
(TAtlasRandom.Bytes(16)) and TAtlasKdf.DefaultPasswordIterations; the fixed values here are a published test
vector.
uses System.SysUtils, Atlas.Crypto;
procedure DerivePasswordKey;
var
Key: TBytes;
begin
Key := TAtlasKdf.Pbkdf2(hkSHA256, TAtlasBytes.OfString('password'), TAtlasBytes.OfString('salt'), 4096, 32);
WriteLn(TAtlasBytes.ToHex(Key));
TAtlasBytes.Wipe(Key);
end;Prints c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a.
Derive separate keys for separate purposes from one strong secret with HKDF. Different Info labels give
unrelated keys.
uses System.SysUtils, Atlas.Crypto;
procedure SplitMasterKey;
var
Master, EncryptionKey, SigningKey: TBytes;
begin
Master := TAtlasRandom.Bytes(32);
EncryptionKey := TAtlasKdf.Hkdf(hkSHA256, Master, nil, TAtlasBytes.OfString('encrypt v1'), 32);
SigningKey := TAtlasKdf.Hkdf(hkSHA256, Master, nil, TAtlasBytes.OfString('sign v1'), 32);
WriteLn(TAtlasBytes.ConstantTimeEquals(EncryptionKey, SigningKey));
TAtlasBytes.Wipe(Master);
end;Prints FALSE.
Make a session token and roll a die, both from the operating system's cryptographic generator.
uses System.SysUtils, Atlas.Crypto;
procedure MakeTokens;
begin
WriteLn(TAtlasRandom.Token);
WriteLn(TAtlasRandom.Below(6) + 1);
end;Prints 43 URL-safe characters (32 random bytes), such as BvArsocl_2qy_UZjodFORgUb2OkAk6ZP2eq0cdyukYg, then
a number from 1 to 6. Below avoids the bias that mod would introduce.
Seal a database field with a key, and tie the box to its record with a context value. The context is authenticated but not stored, so opening needs the same value.
uses System.SysUtils, Atlas.Crypto, Atlas.CryptoBox;
procedure SealAField;
var
Key, Box: TBytes;
begin
Key := TAtlasSecretBox.NewKey;
Box := TAtlasSecretBox.Seal(Key, TAtlasBytes.OfString('account 0042-7719'), TAtlasBytes.OfString('customer:42'));
WriteLn(Length(Box));
WriteLn(TEncoding.UTF8.GetString(TAtlasSecretBox.Open(Key, Box, TAtlasBytes.OfString('customer:42'))));
TAtlasBytes.Wipe(Key);
end;Prints 49 (17 bytes of data plus 32 of overhead) and account 0042-7719. Opening with
customer:43, or with no context, raises EAtlasCryptoError.
Seal text with a password and handle a failure. Passwords are case sensitive.
uses System.SysUtils, Atlas.Crypto, Atlas.CryptoBox;
procedure PasswordNote;
var
Sealed: string;
begin
Sealed := TAtlasSecretBox.SealText('correct horse', 'my note');
try
WriteLn(TAtlasSecretBox.OpenText('Correct horse', Sealed));
except
on E: EAtlasCryptoError do WriteLn(E.Message);
end;
end;Prints Decryption failed: wrong key or password, or the data was altered. Each call runs 600,000 PBKDF2
rounds, so this takes a moment.
Encrypt a file with a key rather than a password. The file functions take a password only, so use the stream
functions on file streams. Unlike EncryptFile, this writes the target directly; write to a temporary name and
rename it if a failure must not leave a partial file.
uses System.SysUtils, System.Classes, System.IOUtils, Atlas.Crypto, Atlas.CryptoBox;
procedure EncryptFileWithKey;
var
Key: TBytes;
Folder, PlainName, SealedName: string;
Source, Dest: TFileStream;
Opened: TBytesStream;
begin
Folder := TPath.Combine(TPath.GetTempPath, 'atlas-crypto-key-file');
TDirectory.CreateDirectory(Folder);
PlainName := TPath.Combine(Folder, 'plan.txt');
SealedName := PlainName + '.sealed';
TFile.WriteAllBytes(PlainName, TAtlasBytes.OfString('Phase one: tests.'));
Key := TAtlasSecretBox.NewKey;
try
Source := TFileStream.Create(PlainName, fmOpenRead or fmShareDenyWrite);
try
Dest := TFileStream.Create(SealedName, fmCreate);
try
TAtlasStreamCrypt.Encrypt(Source, Dest, Key);
finally
Dest.Free;
end;
finally
Source.Free;
end;
WriteLn(TFile.GetSize(SealedName), ' bytes sealed');
Source := TFileStream.Create(SealedName, fmOpenRead or fmShareDenyWrite);
Opened := TBytesStream.Create;
try
TAtlasStreamCrypt.Decrypt(Source, Opened, Key);
WriteLn(TEncoding.UTF8.GetString(Opened.Bytes, 0, Opened.Size));
finally
Opened.Free;
Source.Free;
end;
finally
TAtlasBytes.Wipe(Key);
TDirectory.Delete(Folder, True);
end;
end;Prints 49 bytes sealed (a 16-byte header, the 17 bytes of text and one 16-byte tag), then
Phase one: tests..
Report progress and cancel. The callback runs once per chunk (64 KB by default); setting Cancel stops the work
with an EAtlasCryptoError.
uses System.SysUtils, System.Classes, Atlas.Crypto, Atlas.CryptoBox;
procedure EncryptWithProgress;
var
Source, Dest: TBytesStream;
begin
Source := TBytesStream.Create(TAtlasRandom.Bytes(200000));
Dest := TBytesStream.Create;
try
try
TAtlasStreamCrypt.Encrypt(Source, Dest, TAtlasSecretBox.NewKey,
procedure(Done, Total: Int64; var Cancel: Boolean)
begin
WriteLn(Done, ' of ', Total);
Cancel := Done >= 100000;
end);
except
on E: EAtlasCryptoError do WriteLn(E.Message);
end;
finally
Dest.Free;
Source.Free;
end;
end;Prints 65536 of 200000, 131072 of 200000 and Encryption was cancelled.
Sign data with ECDSA on P-256 and verify it. Verify returns False for a wrong signature, altered data or
another key.
uses System.SysUtils, Atlas.Crypto, Atlas.CryptoKeys;
procedure SignARelease;
var
Pair: TAtlasKeyPair;
Signature: TBytes;
begin
Pair := TAtlasEcdsa.GenerateKeyPair;
try
Signature := TAtlasEcdsa.Sign(Pair.PrivateKey, TAtlasBytes.OfString('release 4.2, build 118'));
WriteLn(TAtlasEcdsa.Verify(Pair.PublicKey, TAtlasBytes.OfString('release 4.2, build 118'), Signature));
WriteLn(TAtlasEcdsa.Verify(Pair.PublicKey, TAtlasBytes.OfString('release 4.3, build 118'), Signature));
finally
Pair.Wipe;
end;
end;Prints TRUE, then FALSE.
Send data to someone who has an RSA key pair: seal the data with a random content key, and wrap that key with the recipient's public key (RSA-OAEP only takes short messages).
uses System.SysUtils, Atlas.Crypto, Atlas.CryptoBox, Atlas.CryptoKeys;
procedure SendToRecipient;
var
Recipient: TAtlasKeyPair;
ContentKey, WrappedKey, Box: TBytes;
begin
Recipient := TAtlasRsa.GenerateKeyPair;
ContentKey := TAtlasSecretBox.NewKey;
Box := TAtlasSecretBox.Seal(ContentKey, TAtlasBytes.OfString('quarterly figures'));
WrappedKey := TAtlasRsa.Encrypt(Recipient.PublicKey, ContentKey);
TAtlasBytes.Wipe(ContentKey);
// The recipient unwraps the content key with the private key and opens the box.
ContentKey := TAtlasRsa.Decrypt(Recipient.PrivateKey, WrappedKey);
WriteLn(TEncoding.UTF8.GetString(TAtlasSecretBox.Open(ContentKey, Box)));
WriteLn(Length(WrappedKey));
TAtlasBytes.Wipe(ContentKey);
Recipient.Wipe;
end;Prints quarterly figures and 384: the default key has 3,072 bits, and the wrapped key is as long as the
modulus. Generating an RSA key pair is slow, so a real recipient makes one once and publishes the public half.
Check a downloaded file against its published checksum with the TAtlasHashCalculator component, created in code
here. The expected value may be in either case and may carry surrounding spaces.
uses System.SysUtils, System.IOUtils, Atlas.Crypto, Atlas.CryptoComponents;
procedure VerifyDownload;
var
Calculator: TAtlasHashCalculator;
FileName: string;
begin
FileName := TPath.Combine(TPath.GetTempPath, 'atlas-crypto-download.txt');
TFile.WriteAllBytes(FileName, TAtlasBytes.OfString('abc'));
Calculator := TAtlasHashCalculator.Create(nil);
try
WriteLn(Calculator.FileMatches(FileName, ' BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD '));
Calculator.Algorithm := hkMD5;
WriteLn(Calculator.HashFile(FileName));
finally
Calculator.Free;
TFile.Delete(FileName);
end;
end;Prints TRUE, then the MD5 value 900150983cd24fb0d6963f7d28e17f72.
Tutorial
Sample project: SealAndOpen.
Goal: hash text against a published known answer, seal a note with a password and with a key, and see every kind of tampering refused.
Create a console application. In the IDE choose File > New > Console Application and save the project as
SealAndOpen. Add the Atlas Cryptosrcfolder to Project > Options > Building > Delphi Compiler > Search path, or install the Atlas Crypto runtime package and build with it. Replace the generated program text with the header below. Theusesclause names the three units the program needs:Atlas.Cryptofor hashes, byte helpers andEAtlasCryptoError,Atlas.CryptoBoxforTAtlasSecretBox, andAtlas.CryptoComponentsforTAtlasHashCalculator. The project compiles, with nothing to run yet.program SealAndOpen; // Atlas Crypto quick start: hash text against a published known answer, seal a // message with a password, open it again, and watch a wrong password, a changed // character and a truncated box all get refused. Finishes on its own with exit // code 0 when every check passes. {$APPTYPE CONSOLE} uses System.SysUtils, Atlas.Crypto, Atlas.CryptoBox, Atlas.CryptoComponents;Add the constants.
Sha256OfAbcis the SHA-256 value ofabcpublished in FIPS 180, which the program will reproduce.Roundslowers the PBKDF2 cost so that the program answers at once; you will remove it when you adapt the program.const // FIPS 180 known answer: SHA-256 of "abc". Sha256OfAbc = 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'; Password = 'correct horse battery staple'; Note = 'Meet at the old mill at noon.'; // Fewer PBKDF2 rounds than the default 600,000 so the program answers at once. // Keep the default in real code; the count travels inside the sealed data. Rounds = 50000;Add two helpers.
Checkprints a passed check and raises on a failed one, so the program verifies itself.Raisesruns an action and reports whether it raisedEAtlasCryptoError, the one exception class that every Atlas Crypto failure uses.procedure Check(Condition: Boolean; const What: string); begin if not Condition then raise Exception.Create('Check failed: ' + What); WriteLn(' ok: ', What); end; // True when Action raises EAtlasCryptoError, the one exception class every // Atlas Crypto failure uses. function Raises(const Action: TProc): Boolean; begin Result := False; try Action(); except on E: EAtlasCryptoError do begin WriteLn(' refused: ', E.Message); Result := True; end; end; end;Hash a known answer.
TAtlasHashCalculatoris a component, but it needs no form: create it with anilowner and free it yourself.HashTextreturns lower-case hexadecimal of the UTF-8 bytes. SettingAlgorithmtohkSHA512switches the hash, andTAtlasHash.OfStringgives the same result as the component, as bytes.procedure HashKnownAnswer; var Calculator: TAtlasHashCalculator; begin WriteLn('1. Hashing'); Calculator := TAtlasHashCalculator.Create(nil); try WriteLn(' SHA-256 of "abc": ', Calculator.HashText('abc')); Check(Calculator.HashText('abc') = Sha256OfAbc, 'matches the published FIPS 180 value'); Calculator.Algorithm := hkSHA512; Check(Length(Calculator.HashText('abc')) = 128, 'SHA-512 gives 64 bytes, 128 hexadecimal digits'); finally Calculator.Free; end; Check(TAtlasBytes.ToHex(TAtlasHash.OfString(hkSHA256, 'abc')) = Sha256OfAbc, 'TAtlasHash.OfString agrees'); end;Seal with a password.
SealTextreturns URL-safe base64 text that carries the salt, the iteration count, the nonce, the ciphertext and the tag.OpenTextgives the note back only for the right password and unaltered text. Character 12 of the sealed text encodes part of the salt, which is authenticated like everything else, so changing it is caught.procedure SealWithPassword; var Sealed, Damaged: string; begin WriteLn('2. Sealing with a password'); Sealed := TAtlasSecretBox.SealText(Password, Note, Rounds); WriteLn(' sealed: ', Sealed); Check(TAtlasSecretBox.OpenText(Password, Sealed) = Note, 'the right password opens it'); Check(TAtlasSecretBox.SealText(Password, Note, Rounds) <> Sealed, 'sealing again gives different text: fresh salt and nonce'); Check(Raises(procedure begin TAtlasSecretBox.OpenText('wrong password', Sealed); end), 'a wrong password is refused'); // Character 12 lies inside the header (the salt), which is authenticated. Damaged := Sealed; if Damaged[12] = 'A' then Damaged[12] := 'B' else Damaged[12] := 'A'; Check(Raises(procedure begin TAtlasSecretBox.OpenText(Password, Damaged); end), 'one changed character is refused'); Check(Raises(procedure begin TAtlasSecretBox.OpenText(Password, Copy(Sealed, 1, Length(Sealed) - 4)); end), 'a truncated box is refused'); end;Seal with a key and a context.
NewKeyreturns 32 random bytes. The contextnote:17is authenticated with the box but not stored in it, so the box opens only where the same context is given.Wipeoverwrites the key and empties the array.procedure SealWithKey; var Key, Box, Plain: TBytes; begin WriteLn('3. Sealing with a key and a context'); Key := TAtlasSecretBox.NewKey; Box := TAtlasSecretBox.Seal(Key, TAtlasBytes.OfString(Note), TAtlasBytes.OfString('note:17')); WriteLn(' ', Length(Box), ' bytes for a ', Length(Note), '-character note'); Plain := TAtlasSecretBox.Open(Key, Box, TAtlasBytes.OfString('note:17')); Check(TEncoding.UTF8.GetString(Plain) = Note, 'the same key and context open it'); Check(Raises(procedure begin TAtlasSecretBox.Open(Key, Box, TAtlasBytes.OfString('note:18')); end), 'another context is refused'); TAtlasBytes.Wipe(Key); Check(Length(Key) = 0, 'the key was wiped'); end;Add the main block and run the program. Any failed check, or any unexpected exception, is printed and sets exit code 1.
begin try HashKnownAnswer; SealWithPassword; SealWithKey; WriteLn('All checks passed.'); except on E: Exception do begin WriteLn(E.ClassName, ': ', E.Message); ExitCode := 1; end; end; end.Check the output. The sealed text differs on every run; everything else is the same:
1. Hashing SHA-256 of "abc": ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad ok: matches the published FIPS 180 value ok: SHA-512 gives 64 bytes, 128 hexadecimal digits ok: TAtlasHash.OfString agrees 2. Sealing with a password sealed: QVNQMQAAw1DcAK3CUexA_OX6gQI7J9gMVdPreRdyw-UkguWhsXnXji4sRNFeOXVPym6vJlGza5kje6TN8ZSVh2ON36ToQVzNbFcYLOo6njU2 ok: the right password opens it ok: sealing again gives different text: fresh salt and nonce refused: Decryption failed: wrong key or password, or the data was altered ok: a wrong password is refused refused: Decryption failed: wrong key or password, or the data was altered ok: one changed character is refused refused: Decryption failed: wrong key or password, or the data was altered ok: a truncated box is refused 3. Sealing with a key and a context 61 bytes for a 29-character note ok: the same key and context open it refused: Decryption failed: wrong key or password, or the data was altered ok: another context is refused ok: the key was wiped All checks passed.Every refusal carries the same message. To adapt the program, remove the
Roundsargument so thatSealTextuses the default 600,000 rounds, then seal your own settings or notes. Use a record id or a column name as the context of a key box, so that a box copied to another record will not open there.
Troubleshooting
Decryption failed: wrong key or password, or the data was altered. One message covers every way opening
can fail, on purpose. Check the inputs one by one: the password (case sensitive, compared as UTF-8), the key,
the context value, and whether the data changed on the way (a re-encoded or line-wrapped text column, a file
transferred in text mode, a truncated field). OpenText ignores white space before and after the text but not
inside it. A key box does not open with the password functions and a password box does not open with a key, and
the same holds for key and password streams.
The key must be 32 bytes; 16 given. Use NewKey, or a password. TAtlasSecretBox and TAtlasStreamCrypt
use AES-256 only. Make keys with TAtlasSecretBox.NewKey, or derive 32 bytes (TAtlasSecretBox.KeySize) with
TAtlasKdf.Pbkdf2 or TAtlasKdf.Hkdf. Only the raw TAtlasAesGcm accepts 16- and 24-byte keys.
Sealing or opening with a password takes a noticeable moment. That is the default 600,000 PBKDF2 rounds at
work, and it is what makes guessing expensive. The cost of opening is chosen by whoever sealed the data, because
the count is stored inside it. For unit tests pass a lower count to SealWithPassword, SealText or
EncryptWithPassword, or set TAtlasFileProtector.Iterations (10,000 at least). In a user interface, run the
call on a background thread; the class functions keep no shared state.
Iterations must be between 10000 and 20000000. Raised when TAtlasFileProtector.Iterations is set outside
that range; the old value is kept. The class functions accept counts from 1 to 20,000,000, for tests, but
TAtlasFileProtector refuses costs too low to protect anything.
Importing the ECDSA_P256 key failed (Windows status C000000D). The blob is not a usable P-256 key of the
expected kind: a private key where a public key belongs, a truncated or re-encoded blob, or a key from the other
algorithm. ECDSA keys (TAtlasEcdsa) and ECDH keys (TAtlasEcdh) are not interchangeable. TAtlasEcdsa.Verify
raises in this case instead of returning False, so a broken setup cannot pass for a bad signature.
Encrypting with RSA (is the message longer than the key allows?) failed (Windows status C000000D).
RSA-OAEP with SHA-256 takes at most the modulus size minus 66 bytes: 190 bytes for a 2048-bit key, 318 for
3072 bits. Seal the data with TAtlasSecretBox and encrypt only its 32-byte key with RSA, as in the examples.
E2003 Undeclared identifier: 'hkSHA256' (or 'EAtlasCryptoError'). TAtlasHashKind and the exception
class are declared in Atlas.Crypto. A unit that uses only Atlas.CryptoComponents or Atlas.CryptoKeys must
add Atlas.Crypto to its uses clause as well.
A file method raises EFOpenError, EFCreateError or EOSError rather than EAtlasCryptoError. Missing,
locked or read-only files are reported by the RTL's file streams and by the final rename, not by Atlas Crypto.
Catch both kinds around file operations. Source and destination must be different files is an
EAtlasCryptoError: the file functions never encrypt a file in place.
The text is not URL-safe base64. TAtlasBytes.FromBase64Url accepts only A-Z, a-z, 0-9, - and
_, without = padding or line breaks. Text from a standard base64 encoder must be converted first. The text
that SealText and EncryptText produce is already in this form.