I'd like to AES-128 encrypt a string in Delphi with a password. I'd like to upload this to my server and be able to decrypt given the same password in C#.
In Delphi, I'm using TurboPower LockBox 3:
function EncryptText_AES_128(input: string; password: string): string;
var
Codec: TCodec;
CipherText: AnsiString;
begin
Codec := TCodec.Create(nil);
try
Codec.CryptoLibrary := TCryptographicLibrary.Create(Codec);
//
Codec.StreamCipherId := BlockCipher_ProgID;
Codec.BlockCipherId := Format(AES_ProgId, [128]);
Codec.ChainModeId := CBC_ProgId;
//
Codec.Password := Password;
Codec.EncryptString(input, CipherText);
//
Result := string(CipherText);
finally
Codec.Free;
end;
end;
How can I decrypt the resulting string in C#? I can change the Delphi code. Nothing is in production yet. I'm not even stuck on using LockBox. But, I would like to avoid putting this in a DLL for P/Invoke.
(My example shows that my encrypted sequence is itself a string. This is not a requirement for me. A stream of bytes is fine.)
Contrary to any troll flame-bait that you might read, LockBox 3 is actually a good quality cryptographic library. The standards compliance of LB3 is impecable. Where you might have problems with interoperability with other languages & libraries is in relation to options that are outside of the standard. If using Lockbox on the Delphi side, then you just need to make sure that these options are handled the same way on the other language's side. If this is not possible, then you should choose another library. I will deal with each of these options below.
There is nothing wrong with the alternative solutions (OpenSSL, CryptoAPI and Eldos). Some of them may be black-box. This might be an issue for some peoople.
Converting password to key. AES-128 uses a 16 byte key. Also the standard mechanism to generate a key from "key data" or "password data" is natively based on a 16 byte input seed. It is safer for interoperability to generate the binary key from the string password on the Delphi side, and just transport the binary key to the other side, rather than transport the string password. This is because the algorithm to convert a string password to a binary 16-byte key is outside the AES standard. Nether-the-less, you can do it either way. When lockbox is given a string password to initialize an AES-128 codec, it looks at the string payload as an array of bytes. If the payload is precisely 16 bytes, then great, it can be passed directly to the AES key generation algorithm, which is specified in the standard. If the string payload is not precisely 16 bytes, then payload will be hashed with SHA-1 to produce a 20 byte hash output. The low 16 bytes of this hash are then passed to the standard AES key generation function. So, your options for ensuring interoperability in relation to key initialization are:
1.1. Transport binary keys instead of string passwords.
1.2. If Option 1.2 is too inconvenient, then transport the password, but mimic the same password-to-key algorithm on the other side.
1.3. If 1 & 2 are not working for some reason, try to restrict passwords to exactly 16 bytes (8 UTF-8 characters or 16 UTF-16 code-points). This should be pretty safe if the other language's implementation is half decent.
UTF-16 versus ansi-string/UTF-8 passwords This is not so much an option, but a trap for young players. We programmers tend to think of "strings" as "strings". But it is not so. In Delphi 2010, the payload of strings are stored in a UTF-16LE encoding with a code-unit size of 2 bytes. But in other languages, such as PHP and python, in the default mode, strings are single-byte code-unit encodings, either UTF-8 or something based on an MS windows code-page base (which MS calls "ansistring"). It pays to remember than UTF-16 encoding of 'mypassword' is not the same as UTF-8 'mypassword'.
IV setup. The AES standard does not deal with the question of how to set up the codec' Initialization Vector (IV). The size of the IV is the same as the size of the underlying block. For AES this is 128 bits or 16 bytes. When encrypting, lockbox creates a 16 byte nonce. This nonce becomes the value of the IV, and it is emitted in the clear at the head of the ciphertext message. Read the documentation on the other side's method/policy for IV initialization. Your options are:
3.1 If the other side prepends the IV to the ciphertext, then you are sweet.
3.2 Otherwise, on the other side, when decrypting, read the first 16 bytes of the ciphertext yourself, and pass the remainder to the foreign codec. Before decryption, tell you foreign codec what the IV is (assuming it's API is capable of this).
Block quantisation The AES block size is 16 bytes. When the plaintext message is not precisely a whole multiple 16 bytes, something must be done to make it a whole multiple. This procedure is called block quantisation and is not dealt with in the standard, but left up to the implementation. Many implementations will use block padding. There is no standard block padding scheme and there are many to choose from. LockBox does not use block padding for CBC (other modes may be a different case). If the plaintext is a whole number of blocks, no quantisation is needed or done, otherwise standard CipherText stealing is used. If the plaintext size is very small (between 1 and 15 bytes) ciphertext stealing is not possible, and a padding scheme is used instead. To ensure interoperability in relation to block quantisation, your options are:
4.1 Check your documentation for the foreign codec in relation to block quantisation (it may come under the heading of "message padding"). If the foreign codec uses ciphertext stealing, then you are sweet (just make sure no short messages).
4.2 Otherwise you could do your own padding. On the lockbox side, lockbox does nothing to messages that are already in whole blocks. Very probably the foreign codec has the same policy - but again you need to check the documentation for the foreign codec.
I just had the same problem. I know this is an old topic, but it helped me a lot. I'm just leaving it here for the record.
Based on Sean's answer, I assume that the mode should be changed to CTS when having more than 1 block. I didn't try it, because 1 block is enough for me, but it should be easy to adapt the code.
I finally found a compatible solution between Delphi and C# for AES-128. It's also works on Wine. Here's my Delphi code:
And here's my C# code:
I was able to successfully implement Troy's Delphi code in 10.2 Tokyo with a couple of modifications.
I removed TNTLxUtils from the Uses as it was not needed (and I didn't have it) and added IdGlobal. The reason for using IdGlobal is that you need to convert the type TBytes to TIdBytes in the Base64_Encode function and TIBytes back to TBytes in Base64_Decode.
Note: This unit will only work in 32-bit applications as it references the 32-bit Windows API.
Thanks, Troy for pointing me in the right direction for a free method of encryption that doesn't require purchasing a toolkit to implement.