RAD Studio Working with cryptography in EurekaLog

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084
Для просмотра ссылки Войди или Зарегистрируйся is an exception tracer, i.e. a tool that installs hooks and catches exceptions being thrown, allowing you to generate a report on unhandled exceptions. However, it does provide various kinds of additional functionality that you can use in your apps. And one of those features is cryptography functions.
EurekaLog offers 3 units:
  1. Для просмотра ссылки Войди или Зарегистрируйся - contains Для просмотра ссылки Войди или Зарегистрируйся;
  2. Для просмотра ссылки Войди или Зарегистрируйся - contains Для просмотра ссылки Войди или Зарегистрируйся;
  3. Для просмотра ссылки Войди или Зарегистрируйся - contains Для просмотра ссылки Войди или Зарегистрируйся.
Although the functions from these units will not be able to fully replace a proper cryptographic support library, it may be enough for you in some special cases.

Important Note: please update EurekaLog to the most recent version. Not all features described here are available in previous versions, because some features were published specifically for this article.

Encoding​

Before talking about cryptography, you need to take decicion about data representation. For example, suppose you want to get the MD5 hash of the 'Привет' string (means "Hello" in Russian, reads as "Privet", stress on the second syllable). What exactly are you gonna feed into hash function? $CF$F0$E8$E2$E5$F2 bytes? (which is 'Привет' encoded via ANSI/Windows-1251) Or $1F$04$40$04$38$04$32$04$35$04$42$04 bytes? ('Привет' in Unicode/UTF-16) Or may be $D0$9F$D1$80$D0$B8 bytes ('Привет' in UTF-8)? Depending on how you answer this question, you will get different results. For example, the MD5 hash for the 'Привет' in UTF-16 would be 8EFA2364EE560EE1B862ECC8D430C9AD, for 'Привет' in ANSI - 43A3F987A7AF93811B7682E43ED0752A, and for 'Привет' in UTF-8 - 8A669E9418750C81AB90AE159A8EC410.

Questions like this probably don't matter if you use cryptography functions exclusively inside your own apps. But as soon as you need to interact with other code - you immediately would have problems with the exact definition of the data.

Therefore, when you want exact result, you should operate on bytes, not strings. In Delphi, to operate on bytes, you can:

Specifically, EurekaLog functions accepts pointer+size, as well as overloaded option for RawByteString.

For example, if you try to obtain MD5-hash from "just" string 'Привет' in PHP - you would get 8a669e9418750c81ab90ae159a8ec410 - i.e. MD5-hash of UTF-8 encoded 'Привет'.
From where you can also conclude that strings in PHP are stored in UTF-8; for comparison: Delphi stores strings as UTF-16 (since Delphi 2009) or ANSI (Delphi 2007 and earlier).
If you want to change the encoding in PHP, you will need to call something like Для просмотра ссылки Войди или Зарегистрируйся. And if you want to change the encoding in Delphi, you need Delphi encoding functions. Specifically, to Для просмотра ссылки Войди или Зарегистрируйся, Для просмотра ссылки Войди или Зарегистрируйся. In Delphi 2009 and up, you can also just declare the string type of the desired encoding and Для просмотра ссылки Войди или Зарегистрируйся.

The same is true in the opposite direction: the result of calling cryptographic functions is a set of bytes (hash, encrypted data, etc.). If you want to display these bytes to a human, you have to convert it to a string. It can be done, again, in different ways. For example, you can use the built-in function Для просмотра ссылки Войди или Зарегистрируйся or its more convenient equivalents: HexEncodeString/HexEncodeToString from EurekaLog. You can use Base64EncodeString/Base64EncodeToString from EurekaLog. If, suddenly, you need to convert data from/to RawByteString, then EurekaLog has RAWToString/RAWFromString helpers. Also you may want to load/save small data directly to files - there is FileToString/StringToFile for that (from the ECompatibility unit).

Examples of using the mentioned functions can be found below.
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084

Hashing​

EurekaLog has functions for calculating the following hashes:
All hashing functions have name like HashNameHash (for example, MD5Hash()), returns result of THashNameHash type (for example, TSHA1Hash), and accepts RawByteString on input, as well as pointer+size (overloaded option).

Additionally, EurekaLog has Для просмотра ссылки Войди или Зарегистрируйся implementation for some hashes. One way to use HMAC is to authenticate a user by combining a Для просмотра ссылки Войди или Зарегистрируйся and a password to obtain hash via HMAC. HMAC functions have names like HashNameHMAC (for example, MD5HMAC()) and accepts password and salt on input.

Here are some practical examples:

1. Calculate hash of a string:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
uses
EEncoding, // for HexEncodeToString
EHash; // for MD5Hash

procedure TForm1.Button1Click(Sender: TObject);
var
S: String; // Source string
UTF8Str: UTF8String; // Byte representation of a string
Hash: TMD5Hash; // Result
begin
// Define source data
S := 'Привет';

// Define exact representation as bytes
// We use UTF-8 in this example
UTF8Str := UTF8Encode(S);
// (you can also just do UTF8Str := S; in Delphi 2009 and up)

// Calculate hash from bytes
Hash := MD5Hash(UTF8Str);

// Show hash to a human
Label1.Caption := HexEncodeToString(@hash, SizeOf(Hash));
// Should display '8A669E9418750C81AB90AE159A8EC410'
end;

2. Calculate hash of a file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
uses
EEncoding, // for HexEncodeToString
EHash, // for SHA256Hash
ECompatibility; // for FileToString

procedure TForm1.Button1Click(Sender: TObject);
var
Content: RawByteString; // File's bytes
Hash: TSHA256Hash; // Result
begin
// Loads entire file into memory
Content := FileToString(ParamStr(0));
// Content will be something like 'MZP'#0#2#0#0#0...

// Calculate hash from bytes
Hash := SHA256Hash(Content);
Finalize(Content); // optional

// Show hash to a human
Label1.Caption := HexEncodeToString(@hash, SizeOf(Hash));
// Should be something like 'FCF52FDC753E3797FE5EE4B5A7680E656D044D6BF7D97C408F0F7874492E43C2'
end;

3. Calculate hash of a string in an arbitrary encoding:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
uses
EEncoding, // for HexEncodeToString (also for TEncoding for older Delphi)
EHash; // for CRC32Hash

procedure TForm21.Button1Click(Sender: TObject);
var
S: String; // Source string
Encoding: TEncoding; // Encoding to encode the string
Content: TBytes; // String's bytes
Hash: TCRC32Hash; // Result
begin
// Define source data
S := 'Привет';

// Define the encoding
Encoding := TEncoding.GetEncoding(866);
// You can also do:
// Encoding := TEncoding.UTF8;
// Encoding := TEncoding.Unicode;
// Encoding := TEncoding.ANSI;
try

// Convert string (characters) to bytes
Content := Encoding.GetBytes(S);

finally
FreeAndNil(Encoding);
end;

// Calculate hash from bytes
Hash := CRC32Hash(Pointer(Content), Length(Content));
Finalize(Content); // optional

// Show hash to a human
Label1.Caption := HexEncodeToString(@hash, SizeOf(Hash));
// Should be '6DB3A7B9'
// You can also do IntToStr(Hash) - which will be 3114775405
end;

4. Check hash in PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
uses
EEncoding, // for HexEncodeToString
EHash, // for MD5Hash
ECore; // for ShellExec

procedure TForm1.Button1Click(Sender: TObject);
var
S: String; // Source string
UTF8Str: UTF8String; // String's bytes
Hash: TMD5Hash; // Hash (bytes)
HashStr: String; // Hash (text)
begin
// Define source data
S := 'Привет';

// Define exact representation as bytes
// We use UTF-8 in this example
UTF8Str := UTF8Encode(S);
// (you can also just do UTF8Str := S; in Delphi 2009 and up)

// Calculate hash from bytes
Hash := MD5Hash(UTF8Str);

// Convert bytes to text
HashStr := HexEncodeToString(@hash, SizeOf(Hash));
// Will be '8A669E9418750C81AB90AE159A8EC410'

// Pass hash as text into PHP script
ShellExec(Format('Для просмотра ссылки Войди или Зарегистрируйся', [HashStr]));
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

// Source data (stored as UTF-8)
$Source = 'Привет';

// Calculate hash of source (UTF-8 encoded) data
// (the function will return text, not bytes)
$Hash = md5($Source);

// Read script's parameter
$HashArg = $_GET['hash'];

// Ensure both passed and calculated hashes match by comparing string representation
if (strtolower($Hash) == strtolower($HashArg)) {
// or you can do this starting with PHP 5.6:
// if (hash_equals($HashArg, $Hash)) {

echo('OK'); // we should get there,
// e.g. source strings match
// 'Привет' in Delphi = 'Привет' in PHP
} else {
echo('FAIL');
}

5. Storing user credentials in a database:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
uses
EHash, // for SHA256HMAC
EEncrypt, // for InitSalt
EEncoding; // for RAWToString and HexEncodeToString/HexDecodeFromString

procedure TForm1.Button1Click(Sender: TObject);
var
UserName: String; // User's login (text)
UserPassword: String; // User's password (text)
UserPasswordRAW: RawByteString; // User's password (bytes)
Salt: TSalt; // Salt (bytes)
SaltStr: String; // Salt (text)
Hash: TSHA256Hash; // Password's hash (bytes)
Hash2: TSHA256Hash; // Hash from database (bytes)
HashStr: String; // Password's hash (text)
begin
// Step 1. Create a new account

// Obtain user's credentials somehow:
UserName := InputBox('Sign in', 'Enter the login:', '');
UserPassword := InputBox('Sign in', 'Enter the password:', '');

// Create random bytes to be used as salt
Salt := InitSalt;

// Convert password (text) to bytes
UserPasswordRAW := UTF8Encode(UserPassword);

// Calculate hash from password (bytes) and salt (bytes) via HMAC
Hash := SHA256HMAC(@Salt, SizeOf(Salt), Pointer(UserPasswordRAW), Length(UserPasswordRAW));

// Convert bytes to text
SaltStr := HexEncodeToString(@Salt, SizeOf(Salt));
HashStr := HexEncodeToString(@hash, SizeOf(Hash));

// Insert a new record into database
// This is a pseudo-code
InsertIntoDBTable('users', ['login', 'salt', 'password'], [UserName, SaltStr, HashStr]);
// Here:
// 'users' - table's name
// 'login' - string field of arbitrary length
// 'salt' - string field of 32 characters or binary field of 16 bytes
// 'password' - string field of 64 characters or binary field of 32 bytes



// Step 2. Authenticate a user

// Obtain user's credentials somehow:
UserName := InputBox('Log in', 'Enter the login:', '');
UserPassword := InputBox('Log in', 'Enter the password:', '');

// Search database for a user with the provided login
// This is a pseudo-code
// Real code should use "arguments"
Query := Format('SELECT salt, password FROM users WHERE login = ''%s'' LIMIT 1', [UserName]);
Values := DBQuery(Query);

// If there is no DB entry - then the login is not correct
if Length(Values) = 0 then
begin
ShowMessage('Invalid login');
Exit;
end;

// Convert salt and hash from text to bytes
SaltStr := Values[0]; // 'salt' field from SELECT
HashStr := Values[1]; // 'password' field from SELECT

Assert(HexCalcDecodedSize(Length(SaltStr)) = SizeOf(Salt));
HexDecodeFromString(SaltStr, @Salt);

Assert(HexCalcDecodedSize(Length(HashStr)) = SizeOf(Hash2));
HexDecodeFromString(HashStr, @Hash2);

// Calculate hash in the same way as above
UserPasswordRAW := UTF8Encode(UserPassword);
Hash := SHA256HMAC(@Salt, SizeOf(Salt), Pointer(UserPasswordRAW), Length(UserPasswordRAW));

// Now we have:
// Hash - hash (bytes) from the entered password
// Hash2 - hash (bytes) from the database
// If both hashes are equal - then password is correct

// Ensure correct password by comparing hashes
if CompareMem(@hash, @Hash2, SizeOf(Hash)) then
ShowMessage('OK')
else
ShowMessage('Invalid password');
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php

// Step 1. Create a new account

// Obtain user's credentials somehow:
$UserName = $_GET['login'];
$UserPassword = $_GET['password'];

// Create random bytes to be used as salt
$Salt = random_bytes(16);

// Calculate hash from password (UTF-8 encoded) and salt (raw bytes) via HMAC
$HashStr = hash_hmac('sha256', $Salt, $UserPassword);

// Convert bytes to text
$SaltStr = bin2hex($Salt);

// Insert a new record into database
// This is a pseudo-code
InsertIntoDBTable('users', ['login', 'salt', 'password'], [$UserName, $SaltStr, $HashStr]);
// Here:
// 'users' - table's name
// 'login' - string field of arbitrary length
// 'salt' - string field of 32 characters or binary field of 16 bytes
// 'password' - string field of 64 characters or binary field of 32 bytes



// Step 2. Authenticate a user

// Obtain user's credentials somehow:
$UserName = $_GET['login'];
$UserPassword = $_GET['password'];

// Search database for a user with the provided login
// This is a pseudo-code
// Real code should use "arguments"
$Query = 'SELECT salt, password FROM users WHERE login = \'' . $UserName . '\' LIMIT 1';
$Values = DBQuery($Query);

// If there is no DB entry - then the login is not correct
if (empty($Values)) {
echo('Invalid login');
die;
}

// Convert salt and hash from text to bytes
$SaltStr = Values['salt']; // 'salt' field from SELECT
$HashStr2 = Values['password']; // 'password' field from SELECT

$Salt = hex2bin($SaltStr);

// Calculate hash in the same way as above
$HashStr = hash_hmac('sha256', $Salt, $UserPassword);

// Now we have:
// $HashStr - hash (text) from the entered password
// $HashStr2 - hash (text) from the database
// If both hashes are equal - then password is correct

// Ensure correct password by comparing hashes (as text)
if (strtolower($HashStr) == strtolower($HashStr2)) {
// or you can do this starting with PHP 5.6:
// if (hash_equals($HashStr2, $HashStr)) {

echo('OK'); // should get there,
// e.g. password is correct
} else {
echo('Invalid password');
}
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084

Encryption​

EurekaLog has the following encryption functions:

Similar to hash functions, encryption functions also accepts pointer+size or RawByteString. However, encryption functions have to return data of arbitrary size too. That is why you can also use the Для просмотра ссылки Войди или Зарегистрируйся record, which just combines pointer and its size into a single argument.

IN-PROCESS ENCRYPTION​

Sometimes an application has to operate on "secret" information (such as user's password). It is necessary to store such sensitive information in an encrypted form to reduce the leakage risks. You can read more about this practice in Для просмотра ссылки Войди или Зарегистрируйся or (highly recommended) Для просмотра ссылки Войди или Зарегистрируйся. EurekaLog offers the following functions to protect sensitive information within a process:
The MemProtect function encrypts the specified memory block within a process in a such a way that it can be decrypted back only from the same process. The MemUnprotect function decrypts info back. The SecureFree function could be used to securely dispose of almost anything. This function will wipe (erase) memory before freeing it.

For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
uses
EEncrypt; // for MemProtect/MemUnprotect and SecureFree

procedure TForm1.Button1Click(Sender: TObject);
var
UserPassword: String;
StoredPassword: TEncryptBuffer;
ClearText: TEncryptBuffer;
begin
// Prepare all buffers
FillChar(StoredPassword, SizeOf(StoredPassword), 0);
FillChar(ClearText, SizeOf(ClearText), 0);

// Obtain some confidential info
UserPassword := InputBox('Query', 'Enter the password:', '');
try

// Encrypt info immediately
ClearText.pbData := Pointer(UserPassword);
ClearText.cbData := Length(UserPassword) * SizeOf(Char);
MemProtect(ClearText, StoredPassword);

finally
// Wipe the confidential info
SecureFree(UserPassword);
// No need to dispose ClearText,
// because we did not allocate memory for it
end;

// ...

// Now we have StoredPassword - encrypted confidential info
// We would need to decrypt it each time we want to use it
// Don't forget to wipe unencrypted info once you have finished using it

// ...

// Decrypt the info:
MemUnprotect(StoredPassword, ClearText);
try

// Use confidential info somehow
Hash := MD5Hash(ClearText.pbData, ClearText.cbData);

finally
// Wipe unencrypted info after use
SecureFree(ClearText);

// You can also wipe derived info
SecureFree(Hash);
end;

// ...

// Clear (encrypted) confidential info when you no longer need it
SecureFree(StoredPassword);
end;

CROSS-PROCESS ENCRYPTION​

Sometimes confidential information has to be stored somewhere. For example, a "Remember me" feature could write login/password pair into registry. The MemProtect/MemUnprotect function will not help you in such cases, because those functions will not work between/across processes (e.g. app's restart means creating a new process). Therefore, EurekaLog offers similar functions: Для просмотра ссылки Войди или Зарегистрируйся and Для просмотра ссылки Войди или Зарегистрируйся. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
uses
EEncrypt, // for DataProtect/DataUnprotect and SecureFree
EConfig, // for RegKeyWrite/RegKeyRead
EEncoding; // for Base64EncodeString/Base64DecodeString

procedure TForm1.Button1Click(Sender: TObject);
var
UserPassword: String;
StoredPassword: RawByteString;
ClearText: RawByteString;
begin
// Obtain some confidential info
UserPassword := InputBox('Query', 'Enter the password:', '');
try

// Convert to RawByteString for convenience
ClearText := UTF8Encode(UserPassword);

// Wipe the original form
SecureFree(UserPassword);

// Encrypt/protect the sensitive info
StoredPassword := DataProtect(ClearText);
// or:
// StoredPassword := DataProtect(ClearText, True);
// if you want to use HKEY_LOCAL_MACHINE below

// Wipe the confidential info
SecureFree(ClearText);

// Convert encrypted bytes to text
UserPassword := Base64EncodeString(StoredPassword);

// Store encrypted confidential info into registry
RegKeyWrite(HKEY_CURRENT_USER, '\SOFTWARE\YourApp', 'SavedPassword', UserPassword);

// Optional
SecureFree(UserPassword);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(UserPassword);
SecureFree(StoredPassword);
SecureFree(ClearText);
end;

// ...

// Now we have encrypted sensitive info stored in the registry
// We need to read and decrypt it each time we want to use it
// Don't forget to wipe it after usage

// ...

// Read (encrypted) confidential info from the registry
UserPassword := RegKeyRead(HKEY_CURRENT_USER, '\SOFTWARE\YourApp', 'SavedPassword', '');
try

// Convert text back to bytes
StoredPassword := Base64DecodeString(UserPassword);

// Optional
SecureFree(UserPassword);

// Decrypt confidential info
ClearText := MemUnprotect(StoredPassword);

// Optional
SecureFree(StoredPassword);

// Need to convert back to text
UserPassword := UTF8ToString(ClearText);

// Wipe the unsecured data
SecureFree(ClearText);

// Use sensitive info somehow
Hash := MD5Hash(Pointer(UserPassword), Length(UserPassword) * SizeOf(Char));

// Wipe once finished
SecureFree(UserPassword);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(UserPassword);
SecureFree(StoredPassword);
SecureFree(ClearText);
end;
end;
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084

SYMMETRIC ENCRYPTION​

EurekaLog supports Для просмотра ссылки Войди или Зарегистрируйся and Для просмотра ссылки Войди или Зарегистрируйся symmetric ciphers. Both ciphers are not patented and can be used in any app. TEA is used in a wide variety of hardware due to its extremely low memory requirements and ease of implementation. Twofish is a robust general purpose symmetric encryption algorithm.

Note that encrypted data can be larger than the original data - since symmetric encryption algorithms often operate in blocks of data ("chunks"). A distinctive feature of TEA is that the encrypted data will be equal in size to the original data, so the TEA encryption/decryption functions have an overloaded option for in-place operations, i.e. without memory reallocation. For the Twofish algorithm, the data size must be a multiple of the block size (16 bytes) - otherwise the data will be padded with the PKCS#5 algorithm to a minimum required size.

EurekaLog offers algEncrypt/algDecrypt functions for both ciphers. All functions accepts key and source data. The difference is that Twofish functions allow you to additionally specify an optional Для просмотра ссылки Войди или Зарегистрируйся. Initialization vector is just a set of random bytes (which you can create by calling the Для просмотра ссылки Войди или Зарегистрируйся), which ensures that two equal source data will look different after encryption, e.g. it is something like Для просмотра ссылки Войди или Зарегистрируйся for hash. You don't need to store initialization vector securely, when used - it should be stored/transmitted together with encrypted data.

Both cipers use binary keys (e.g. bytes) for encryption. Because cipers are "symmetric", this means that decryption key must be the same as encryption key. Obviosly, TEA key and Twofish key have different (but fixed) size. As a rule, encryption keys are not random, but are obtained from passwords entered by a user. To convert an arbitrary password into a key of a fixed length, the algDeriveKey functions are used, which accepts a block of data of an arbitrary size. Key is derived from a password by a simple call to the hash function with an appropriate size result. For example, for TEA it will be MD5, for Twofish it will be SHA-256. The derive functions also have an overload that accepts the password as a string with an optional Для просмотра ссылки Войди или Зарегистрируйся. In this case, the password is converted to UTF-8 representation, and the hash is taken from the string 'salt' + 'UTF-8 password'. In addition, there is another version of derive functions: algDeriveKeyHMAC function, which uses the HMAC algorithm to combine salt and password. In general, if you plan to use salt - we recommend using the algDeriveKeyHMAC functions.

If the encryption keys are obtained in some other way (e.g. not from a password) - you can also exchange keys directly, without "deriving" them from passwords. Just treat the key as a fixed size record/bytes array. The only pitfall is that EurekaLog uses optimization with Twofish: the key is not used directly, but is first converted into an intermediate version, which allows optimizing encryption and decryption operations. The original key is named TTwofishRAWKey and the optimized version is TTwofishKey.

For example:

1. Encryption/decryption using a password:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
uses
EEncrypt; // for InitSalt, TEADeriveKey, TEAEncrypt/TEADecrypt, SecureFree

procedure TForm1.Button1Click(Sender: TObject);
var
Salt: TSalt; // Salt (random bytes)
Key: TTEAKey; // Key (derived from password)
Source: String; // Source text
SourceBytes: RawByteString; // Source text (bytes)
EncryptedBytes: RawByteString; // Encrypted text
begin
// Prepare all buffers
FillChar(Salt, SizeOf(Salt), 0);
FillChar(Key, SizeOf(Key), 0);

// Step 1: Encryption
try

// Define source data
Source := 'Привет';

// Define exact representation as bytes
// We use UTF-8 in this example
SourceBytes := UTF8Encode(Source);

// No need source data anywore - wipe it
SecureFree(Source);

// Generate random bytes to be used as salt
Salt := InitSalt;

// Derive key from password and salt
Key := TEADeriveKeyHMAC('a super secret password', Salt);

// Encrypt source data
EncryptedBytes := TEAEncrypt(Key, SourceBytes);

// Wipe everything that we no longer need
SecureFree(Key);
SecureFree(SourceBytes);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(Source);
SecureFree(SourceBytes);
SecureFree(Key);
end;

// How we have:
// Salt - salt to derive key from password
// EncryptedBytes - encrypted data (arbitrary size)
// Both of those should be transferred to decryption side

// Step 2: Decryption
try
// Derive key from password and salt (salt should be passed to us)
Key := TEADeriveKeyHMAC('a super secret password', Salt);
// Now Key must match encryption key exactly

// Decrypt encrypted data
SourceBytes := TEADecrypt(Key, EncryptedBytes);

// Wipe key
SecureFree(Key);

// Optional
SecureFree(EncryptedBytes);

// Convert data back to text
Source := UTF8ToString(SourceBytes);

// Wipe unneeded data
SecureFree(SourceBytes);

// Use source data somehow
ShowMessage(Source);

// Wipe source data after usage
SecureFree(Source);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(Source);
SecureFree(SourceBytes);
SecureFree(EncryptedBytes);
SecureFree(Key);
SecureFree(Salt);
end;
end;

2. Exchange encrypted data between Delphi and PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
uses
EEncrypt, // for all Twofish functions and SecureFree
EEncoding, // for Base64EncodeToString
ECore; // for ShellExec

procedure TForm1.Button1Click(Sender: TObject);
const
// Secret key known to both parties
// It must match key in PHP-script exactly
// Basically, this is just random bytes, which you can generate via TwofishInitSessionKeyRAW
// Obviosly, if you are going to use this example - you MUST replace this constant
// This is just an example. A real app may store this key somewhere
SecretKey: TTwofishRAWKey =
(160, 22, 228, 9, 73, 192, 173, 149,
154, 19, 115, 215, 74, 36, 20, 202,
178, 26, 103 , 47, 51, 4, 144, 20,
73, 153, 49, 160, 192, 25, 20, 114);
var
Key: TTwofishKey; // Optimized key (created from constant above)
IV: TTwofishInitVector; // Initialization vector (bytes)
Text: String; // Source data (text)
TextRAW: RawByteString; // Source data (bytes)
EncryptedText: RawByteString; // Encrypted data (bytes)
EncodedIV: String; // Initialization vector (text)
EncodedText: String; // Encrypted data (text)
URL: String; // URL to call PHP-script
ReplyRAW: RawByteString; // Reply from PHP-script (bytes)
Reply: String; // Reply from PHP-script (text)
begin
// Prepare buffers
FillChar(Key, SizeOf(Key), 0);
FillChar(IV, SizeOf(IV), 0);
try

// Obtain source data somehow
Text := 'Привет!';

// Convert text to bytes. Use UTF-8 in this example
TextRAW := UTF8Encode(Text);

// No longer needed
SecureFree(Text);

// Prepare (optimize) the key
Key := TwofishInitKey(SecretKey);

// Generate random bytes to be used as initialization vector
IV := TwofishInitIV;

// Encrypt source data
// This will use CBC mode, because initialization vector is used
EncryptedText := TwofishEncrypt(Key, TextRAW, @IV);

// Wipe source text
SecureFree(TextRAW);

// Convert bytes to text
EncodedIV := Base64EncodeToString(@IV, SizeOf(IV));
EncodedText := Base64EncodeString(EncryptedText);

// Optional
SecureFree(EncryptedText);

// Crearte URL to call PHP-script
// URLEncode is required to escape the '+' character
// If you are going to use HEX-encoding instead of Base64 - then URLEncode is not required
URL := Format('Для просмотра ссылки Войди или Зарегистрируйся', [URLEncode(EncodedIV), URLEncode(EncodedText)]);

// Optional
SecureFree(EncodedIV);
SecureFree(EncodedText);

// Call PHP-script
if not InitWebTools then
RaiseLastOSError;
try
ReplyRAW := InternetGet(URL, [], []);
finally
DoneWebTools;
end;

// PHP-script did not returned anything?
if ReplyRAW = '' then
Abort;

// Optional
SecureFree(URL);

// Convert text to bytes
ReplyRAW := Base64DecodeString(Trim(String(ReplyRAW)));

// Decrypt data
TextRAW := TwofishDecrypt(Key, ReplyRAW, @IV);

// No longer needed
SecureFree(Key);
SecureFree(IV);

// Optional
SecureFree(ReplyRAW);

// Convert bytes back to text
Text := UTF8ToString(TextRAW);

// No longer needed
SecureFree(TextRAW);

// Use reply from PHP-script
ShowMessage(Text);
// Should show:
// 'Hello from PHP: Привет'

// Wipe the reply after use
SecureFree(Text);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(Text);
SecureFree(TextRAW);
SecureFree(Key);
SecureFree(IV);
SecureFree(EncryptedText);
SecureFree(EncodedIV);
SecureFree(EncodedText);
SecureFree(URL);
SecureFree(ReplyRAW);
SecureFree(Reply);
end;
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php

// The functions below are required, because MCrypt use zero-padding instead of PKCS#5
// While OpenSSL does support PKCS#5, but it does not support Twofish

// PKCS#5 padding
function pkcs5_pad($text, $blocksize = 16) {
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}

// PKCS#5 removal (unpadding)
function pkcs5_unpad($text) {
$pad = ord($text{strlen($text)-1});
if ($pad > strlen($text)) {
return false;
}
if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
return false;
}
return substr($text, 0, -1 * $pad);
}

// Secret key known to both parties
// It must match key in Delphi exactly
// Basically, this is just random bytes, which you can generate via random_bytes(32)
// Obviosly, if you are going to use this example - you MUST replace this constant
// This is just an example. A real app may store this key somewhere
$Key = pack('C*',
160, 22, 228, 9, 73, 192, 173, 149,
154, 19, 115, 215, 74, 36, 20, 202,
178, 26, 103 , 47, 51, 4, 144, 20,
73, 153, 49, 160, 192, 25, 20, 114);

// Read passed data (initialization vector and encrypted data)
$EncodedIV = $_GET['iv'];
$EncodedText = $_GET['text'];

// Convert text to bytes
$IV = base64_decode($EncodedIV);
$EncryptedText = base64_decode($EncodedText);

// Decrypt data (you must use MCRYPT_TWOFISH256 instead of MCRYPT_TWOFISH in some versions of PHP)
$Text = mcrypt_decrypt(MCRYPT_TWOFISH, $Key, $EncryptedText, MCRYPT_MODE_CBC, $IV);

// Trim data to its real size
$Text = pkcs5_unpad($Text);

// Do something with source data from Delphi
$Text = 'Hello from PHP: ' . $Text;

// Pad data up to chunk border
$Text = pkcs5_pad($Text);

// Encrypt source data (you must use MCRYPT_TWOFISH256 instead of MCRYPT_TWOFISH in some versions of PHP)
$EncryptedText = mcrypt_encrypt(MCRYPT_TWOFISH, $Key, $Text, MCRYPT_MODE_CBC, $IV);

// Convert bytes to text
$EncodedText = base64_encode($EncryptedText);

// Send encrypted data back to Delphi
echo($EncodedText);
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084
3. Storing file encrypted:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
uses
EEncrypt, // for all Twofish functions and SecureFree
EEncoding, // for HexEncodeToString/HexDecodeString
EConfig, // for RegKeyWrite/RegKeyRead
ECompatibility; // for FileToString/StringToFile

procedure TForm1.Button1Click(Sender: TObject);
var
RAWKey: TTwofishRAWKey; // Encryption/decryption (session) key (RAW bytes)
Key: TTwofishKey; // Encryption/decryption (session) key (optimized)
IV: TTwofishInitVector; // Initialization vector
Content: RawByteString; // Source file (bytes)
EncryptedData: RawByteString; // Encrypted file
DataClear: RawByteString; // To encrypt encryption/session key
DataEncrypted: RawByteString; // To encrypt encryption/session key
DataStr: String; // Encrypted encryption/session key (text)
begin
// Prepare all buffers
FillChar(RAWKey, SizeOf(RAWKey), 0);
FillChar(Key, SizeOf(Key), 0);
FillChar(IV, SizeOf(IV), 0);

// Step 1: encrypt file on disk with random key
try

// Create random key (random bytes)
RAWKey := TwofishInitSessionKeyRAW;

// Optimize it for further use
Key := TwofishInitKey(RAWKey);

// Load whole file into a string
Content := FileToString(ParamStr(0));
// Content will be like 'MZP'#0#2#0#0#0...

// Use random bytes as initialization vector
IV := TwofishInitIV;

// Encrypt data (e.g. file)
EncryptedData := TwofishEncrypt(Key, Content, @IV);

// Wipe not needed data
SecureFree(Content);
SecureFree(Key);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(Content);
SecureFree(Key);
end;

// Now we have:
// RAWKey - (secret) session key, which was used to encrypt the file
// IV - (open) initialization vector
// EncryptedData - encrypted file

// Step 2: protect session key
try

// Convert to RawByteString for convenience
DataClear := RAWToString(@RAWKey, SizeOf(RAWKey));

// Wipe key (no longer needed)
SecureFree(RAWKey);

// Encrypt encryption key
DataEncrypted := DataProtect(DataClear);

// Wipe unneccessary data
SecureFree(DataClear);

// Convert bytes to text
DataStr := HexEncodeToString(Pointer(DataEncrypted), Length(DataEncrypted));

// Optional
SecureFree(DataEncrypted);

// Store encrypted session key into registry
// We can safely do that, because this key can be decrypted back by the same user account only
RegKeyWrite(HKEY_CURRENT_USER, '\SOFTWARE\YourApp', 'EncryptionKey', DataStr);

// Optional
SecureFree(DataStr);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(RAWKey);
SecureFree(DataClear);
SecureFree(DataEncrypted);
SecureFree(DataStr);
end;

// Now we have:
// Encrypted (secret) session (encryption) key in the registry
// IV - (open) initialization vector
// EncryptedData - encrypted file
// So, if we want to save encrypted file to disk - we need to save both IV and EncryptedData
// Only current user account would be able to decrypt data back

// Step 3: decrypt session key
try

// Read stored session key (as text)
DataStr := RegKeyRead(HKEY_CURRENT_USER, '\SOFTWARE\YourApp', 'EncryptionKey', '');

// Convert text to bytes
DataEncrypted := HexDecodeString(DataStr);

// Optional
SecureFree(DataStr);

// Decrypt session key
DataClear := DataUnprotect(DataEncrypted);

// Optional
SecureFree(DataEncrypted);

// Prepare session key (bytes)
Assert(Length(DataClear) = SizeOf(RAWKey));
RAWFromString(DataClear, @RAWKey);

// Wipe it's copy
SecureFree(DataClear);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(DataStr);
SecureFree(DataEncrypted);
SecureFree(DataClear);
end;

// Now we have:
// RAWKey - (secret) key, which was used to encrypt the file
// IV - (open) initialization vector
// EncryptedData - encrypted file

// Step 4: decrypt the file
try

// Optimize the key for usage
Key := TwofishInitKey(RAWKey);

// Wipe source (no longer needed)
SecureFree(RAWKey);

// Decrypt the data (file)
// Both Key and IV must be exactly the same as in encryption at step 1 above
Content := TwofishDecrypt(Key, EncryptedData, @IV);

// Wipe the key (no longer needed)
SecureFree(Key);

// Optional
SecureFree(EncryptedData);

// Store unencrypted data back to file
StringToFile(ParamStr(0) + '.copy', Content);
// Now the Project1.exe.copy file must be exact copy of Project1.exe

// Wipe unnecessary data
SecureFree(Content);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(RAWKey);
SecureFree(Key);
SecureFree(EncryptedData);
SecureFree(Content);
end;
end;
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084

ASYMMETRIC CRYPTOGRAPHY​

For asymmetric encryption - EurekaLog supports Для просмотра ссылки Войди или Зарегистрируйся. The algorithm is not proprietary and can be freely used in any app. The asymmetry of the algorithm means that it uses two different keys: one key is used for encryption, the other key is used for decryption. One of the keys is kept secret, it is called "secret" or "private" key, and the other can be published - it is called "public" or "open" key. Whatever is encrypted with the public key can only be decrypted back with the matched private key and vice versa.

Key Management​

EurekaLog stores RSA keys into the Для просмотра ссылки Войди или Зарегистрируйся, which have two fields: Для просмотра ссылки Войди или Зарегистрируйся to store public key and Для просмотра ссылки Войди или Зарегистрируйся to store private key. You can create a brand new pair of keys by calling the Для просмотра ссылки Войди или Зарегистрируйся (which will take a while - say, 5-15 seconds). As a rule, keys are not generated in apps, but ready-made (pre-generated) keys are loaded. EurekaLog offers the RSALoad/SavePublic/PrivateKey functions to load and save keys, for example: Для просмотра ссылки Войди или Зарегистрируйся. EurekaLog supports few formats to export/import the keys, which are described by the Для просмотра ссылки Войди или Зарегистрируйся:
  • rsBLOB - this is a binary representation of a key with a header. The Для просмотра ссылки Войди или Зарегистрируйся from Microsoft is used as the header. Little-Endian.
  • rsDER - this is a binary representation of a key, encoded into ASN.1 container without any headers (so called PKCS#1), Big-Endian. If you attempt to load ASN.1 container with a header (PKCS#8) - it will be silently ignored. It matches the Для просмотра ссылки Войди или Зарегистрируйся in CryptoAPI. As a rule, this format is used to save into .der files.
  • rsPEM - this is a textual representation of a key. Basically, it is the same rsDER, but encoded into Base64. As a rule, this format is used to save into .pem files. .key, .cert, .cer, or .crt are also used. PKCS#1 compliant files use headers like -----BEGIN/END RSA PRIVATE KEY-----, -----BEGIN/END RSA PUBLIC KEY-----, while PKCS#8 complient files use headers like -----BEGIN/END PRIVATE KEY-----, -----BEGIN/END PUBLIC KEY-----.
EurekaLog does not support encrypted PKCS#1/PKCS#8, nor PKCS#7 and PKCS#12. PKCS#8 is supported by EurekaLog, but only when importing (see below).

For example, the very same key can be exported like the following:
  • rsBLOB (the PUBLICKEYBLOB/PRIVATEKEYBLOB header + little-endian, key $25 $17 $B4 $A0 ... $96 $B9 $9C $E7 starts from byte $15/21 and ends at the file's end):
    Для просмотра ссылки Войди или Зарегистрируйся
  • rsDER (ASN.1-container PKCS#1 + big-endian, e.g. the same key $E7 $9C $B9 $96 ... $A0 $B4 $17 $25 starts with byte 10 and ends at byte 6 from file's end):
    Для просмотра ссылки Войди или Зарегистрируйся
  • rsPEM (Base64-encoded ASN.1-container PKCS#1):
    -----BEGIN RSA PUBLIC KEY-----
    MIICCgKCAgEA55y5ll1KryRC7umxntWX7t3zOP3qUVxQo7gin3sA1dePyzLxTxtE
    47R+/sqkgFygXdlBqnmjbwu60kU2Zd7k7QFGhZWqfPcAYI3xd660vUPnmXK7n2R1
    3AtF2BW/5MqIH7D3ddjLCt5CoUn6KRZSuz+pySDpuquKerRB5Gq/0WjUG2IIcQXU
    Z1i4qMicPhbOJH76rFPgRngBuvJtS0UCBKx4YOlK0q1JUUJ1leSGp2gAjYGrD7fN
    SOU8r70a97NDu4UblmsS9zW29OHAEF7jNFsVNVBU78P/XZ4hmL41gaPRGws3HXfA
    vGbVattUzHTHsHMJeRLoiPAgak3TqAM2px7qOcNNN8FB91XbnxzvPARfDrBMbpc4
    OcWmDSMuc1RGI/mQCIlGRvA2nhD7Dfu3L5sxnrjjOC+LLpIVsGe5+cs1ZkfD7kII
    AzV/MXXNlx366n/Z1+u97VocmvHcqVCl/s9AMqdXflzAYD+9p7bXhJdP9XfOXf9z
    zCyPBK/Iyk+B4lRR9cmuBW7FAq1JM3PZWZ2mEx0fgrL8M0w5cf2Ts84XtNIEFDa6
    MFOe48sJfDIiPPw4ePohSuYpAY71Du2cQe87VQAf/caclWsrFplItilN93Xx5kQW
    5S16HHLc7A+EKEaNBnUsNl+n0/99jjfHA9PAqxFVaVT68X9eSKC0FyUCAwEAAQ==
    -----END RSA PUBLIC KEY-----

Creating and exporting a new key pair:
  1. Delphi (PKCS#1):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    uses
    EEncrypt; // for all RSA functions

    procedure TForm1.Button1Click(Sender: TObject);
    var
    Key: TRSAKey;
    begin
    // Create a new key pair (takes few seconds)
    Key := RSAGenKey;
    try

    // Export both keys into all formats (only as an example)
    RSASavePublicKey(Key, 'C:\Documents\public.blob', rsBLOB);
    RSASavePublicKey(Key, 'C:\Documents\public.der', rsDER);
    RSASavePublicKey(Key, 'C:\Documents\public.pem', rsPEM);

    RSASavePrivateKey(Key, 'C:\Documents\private.blob', rsBLOB);
    RSASavePrivateKey(Key, 'C:\Documents\private.der', rsDER);
    RSASavePrivateKey(Key, 'C:\Documents\private.pem', rsPEM);

    finally
    // Wipe all keys
    SecureFree(Key);
    end;
    end;
  2. PHP (PKCS#8):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?php

    // Create a new key pair (takes few seconds)
    $config = array(
    "private_key_bits" => 4096,
    "private_key_type" => OPENSSL_KEYTYPE_RSA,
    "encrypt_key" => false
    );
    $privateKey = openssl_pkey_new($config);
    $publicKey = openssl_pkey_get_public($privateKey);

    // Save keys into files
    openssl_pkey_export($privateKey, $PEM, null, $config);
    file_put_contents('./private.pem', $PEM);
    file_put_contents('./public.pem', $publicKey['key']);
  3. OpenSSL (PKCS#8):
    openssl genpkey -out private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:4096
    openssl rsa -in private.pem -out public.pem -outform pem -pubout

If you want to exchange keys between Delphi/EurekaLog, Windows/WinCrypt, and PHP/OpenSSL - you need to use an ASN.1-container format (i.e. binary DER or text PEM). There is one pitfall here:
  1. Windows/WinCrypt exports/imports only keys themselfes into ASN.1-container. Such format is called PKCS#1, you can distinguish it in a text form (PEM) by the comments like -----BEGIN RSA PUBLIC KEY-----. If you open such PEM/DER file into any ASN.1 editor/viewer/decoder (or use the openssl asn1parse -in private.pem command) - you should see something like this:SEQUENCE (9 elem)
    INTEGER 0
    INTEGER (4096 bit) 758666102228921792910938751013886686183781000609266742264329283135491…
    INTEGER 65537
    INTEGER (4096 bit) 626038158727742962915964533848528190012037116635627896022932505790124…
    INTEGER (2048 bit) 265173868639039499374763037151257212413358779720904507462546669893261…
    INTEGER (2048 bit) 286101381754789259717271171537486143896554619163648315398099842455950…
    INTEGER (2048 bit) 241273140164272477344356926702923044634459679795497746005945617372402…
    INTEGER (2047 bit) 117977475958972484914769571551956345862709440207784850140129213152449…
    INTEGER (2047 bit) 114103066753170488160676998988478007480145103422326665392933549037964…
    8 feilds describe various components of the key (module, exponent, etc.).
  2. PHP/OpenSSL exports not just keys, but also stores additional information, such as alg ID, version, etc. Such format is called PKCS#8, you can distinguish it in a text form (PEM) by the comments like -----BEGIN PRIVATE KEY-----. If you open such PEM/DER file into any ASN.1 editor/viewer/decoder - you should see something like this:SEQUENCE (3 elem)
    INTEGER 0
    SEQUENCE (2 elem)
    OBJECT IDENTIFIER 1.2.840.113549.1.1.1 rsaEncryption (PKCS #1)
    NULL
    OCTET STRING (2349 byte) 30820929020100028202010098D61F2FBA3EC958DB082F286781EE7CC258ADCE2B0A…
    SEQUENCE (9 elem)
    INTEGER 0
    INTEGER (4096 bit) 758666102228921792910938751013886686183781000609266742264329283135491…
    INTEGER 65537
    INTEGER (4096 bit) 626038158727742962915964533848528190012037116635627896022932505790124…
    INTEGER (2048 bit) 265173868639039499374763037151257212413358779720904507462546669893261…
    INTEGER (2048 bit) 286101381754789259717271171537486143896554619163648315398099842455950…
    INTEGER (2048 bit) 241273140164272477344356926702923044634459679795497746005945617372402…
    INTEGER (2047 bit) 117977475958972484914769571551956345862709440207784850140129213152449…
    INTEGER (2047 bit) 114103066753170488160676998988478007480145103422326665392933549037964…
    The key is stored in the same way as in PKCS#1, but there is also an additional header added.
Different encryption libraries support various formats. If you attempt to pass key in an unsupported format - you would get errors like these:CRYPT_E_ASN1_BADTAG (8009310B): ASN1 bad tag value met
openssl_pkey_get_private: error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag

EurekaLog saves keys into PKCS#1, but it is able to load both PKCS#1 and PKCS#8. OpenSSL saves into PKCS#8, but it supports loading both PKCS#8 and PKCS#1.

IMPORTANT
OpenSSL supports PKCS#1/PKCS#8 PEM only when it has the proper comment! E.g. the PKCS#1 file must start with the -----BEGIN RSA PRIVATE KEY----- or -----BEGIN RSA PUBLIC KEY-----, while PKCS#8 file must start with the -----BEGIN PRIVATE KEY----- or -----BEGIN PUBLIC KEY-----.

P.S. You can also convert PKCS#1 (Windows/WinCrypt/EurekaLog) into PKCS#8 (PHP/OpenSSL) via:
openssl pkcs8 -topk8 -inform pem -in private.pem -outform pem -nocrypt -out private2.pem
openssl rsa -RSAPublicKey_in -in public.pem -pubout -out public2.pem
and back:eek:penssl rsa -inform pem -in private.pem -outform pem -out private2.pem
openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public2.pem

P.P.S. Latest versions of OpenSSL supports BLOB:
Convert PEM to BLOB:
openssl rsa -inform PEM -in private.pem -outform "MS PRIVATEKEYBLOB" -out private.blob
Convert BLOB to PEM:
openssl rsa -inform "MS PRIVATEKEYBLOB" -in private.blob -outform PEM -out private.pem
Change key format to "MS PUBLICKEYBLOB" for a public key, or you can simply convert private key, then extract public key from the private key.
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084
In summary, we recommend:
  1. Use the EurekaLog Crypto Helper tool to create a pair of new RSA keys:
    1. Run Start / Programs / EurekaLog / Tools / EurekaLog Crypto Helper
    2. Go to the Keys tab
    3. Go to the RSA tab
    4. Hit Create New button
    5. Save private and public keys into private.pem and public.pem files. This will save keys into PKCS#1.
    Alternatively you can use:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    uses
    EEncrypt;

    procedure TForm1.Button1Click(Sender: TObject);
    var
    Key: TRSAKey;
    begin
    Key := RSAGenKey;
    try
    RSASavePublicKey (Key, 'C:\Documents\public.pem', rsPEM);
    RSASavePrivateKey(Key, 'C:\Documents\private.pem', rsPEM);
    finally
    SecureFree(Key);
    end;
    end;
  2. Import keys into Delphi (EurekaLog supports PKCS#1):
    1
    2
    3
    4
    5
    var
    RSAKey: TRSAKey;
    begin
    // Loads PKCS#1 PEM
    RSAKey := RSALoadPrivateKey('C:\Documents\private.pem', rsPEM);
    1
    2
    3
    4
    5
    var
    RSAKey: TRSAKey;
    begin
    // Loads PKCS#1 PEM
    RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
  3. Import keys into PHP (OpenSSL from PHP supports PKCS#1):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?php

    $PrivateKey = <<<EOD
    -----BEGIN RSA PRIVATE KEY-----
    MIIJKgIBAAKCAgEA5CBBmb8QlDVt7uqKZPW6I/GcjyzDg+7VuZd5OcQXVpglsWoa
    ...
    aNz1gCLcqrQiTXHTVg821kYszBDySjfQGJQ3JJhf1/9XGcVjcopbWWeeNpHs5w==
    -----END RSA PRIVATE KEY-----
    EOD;

    // Or:
    // $PrivateKey = 'file:///var/www/private.pem';

    $PrivateKey = openssl_pkey_get_private($PrivateKey);
    if (!PrivateKey) {
    echo('openssl_pkey_get_private: ' . openssl_error_string());
    die();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?php

    $PublicKey = <<<EOD
    -----BEGIN RSA PUBLIC KEY-----
    MIICCgKCAgEA5CBBmb8QlDVt7uqKZPW6I/GcjyzDg+7VuZd5OcQXVpglsWoauYvq
    ...
    f69nl8KyfHhsqffkDeDIaA73hspgFM5bh2zGdj4n8101bjHRu8N35qECAwEAAQ==
    -----END RSA PUBLIC KEY-----
    EOD;

    // Or:
    // $PublicKey = 'file:///var/www/public.pem';

    $PublicKey = openssl_pkey_get_public($PublicKey);
    if (!PublicKey) {
    echo('openssl_pkey_get_public: ' . openssl_error_string());
    die();
    }
    It is important that keys must start with a correct comment. E.g. -----BEGIN RSA PRIVATE KEY----- and -----BEGIN RSA PUBLIC KEY-----.
Alternatively, you can:
  1. Для просмотра ссылки Войди или Зарегистрируйся.
  2. Create a new key pair. The commands below will create two files (private.pem and public.pem) as PKCS#8 PEM:
    openssl genpkey -out private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:4096
    openssl rsa -in private.pem -out public.pem -outform pem -pubout
  3. Import keys in Delphi (EurekaLog supports PKCS#8):
    1
    2
    3
    4
    5
    var
    RSAKey: TRSAKey;
    begin
    // Loads PKCS#8 PEM
    RSAKey := RSALoadPrivateKey('C:\Documents\private.pem', rsPEM);
    1
    2
    3
    4
    5
    var
    RSAKey: TRSAKey;
    begin
    // Loads PKCS#8 PEM
    RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
  4. Import keys in PHP (OpenSSL in PHP supports PKCS#8):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?php

    $PrivateKey = <<<EOD
    -----BEGIN PRIVATE KEY-----
    MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDkIEGZvxCUNW3u
    ...
    X+VZcAA+CUNp1xYUuep0UoeqYtfzTuKvinNo3PWAItyqtCJNcdNWDzbWRizMEPJK
    N9AYlDckmF/X/1cZxWNyiltZZ542kezn
    -----END PRIVATE KEY-----
    EOD;

    // Or:
    // $PrivateKey = 'file:///var/www/private.pem';

    $PrivateKey = openssl_pkey_get_private($PrivateKey);
    if (!PrivateKey) {
    echo('openssl_pkey_get_private: ' . openssl_error_string());
    die();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?php

    $PublicKey = <<<EOD
    -----BEGIN PUBLIC KEY-----
    MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5CBBmb8QlDVt7uqKZPW6
    ...
    v4sIP6NMmNKN8TwtqUKxcjZJMrVhjPJWf69nl8KyfHhsqffkDeDIaA73hspgFM5b
    h2zGdj4n8101bjHRu8N35qECAwEAAQ==
    -----END PUBLIC KEY-----
    EOD;

    // Or:
    // $PublicKey = 'file:///var/www/public.pem';

    $PublicKey = openssl_pkey_get_public($PublicKey);
    if (!PublicKey) {
    echo('openssl_pkey_get_public: ' . openssl_error_string());
    die();
    }
    It is important that keys must start with a correct comment. E.g. -----BEGIN PRIVATE KEY----- and -----BEGIN PUBLIC KEY-----.

Note: the openssl_pkey_get_private/openssl_pkey_get_public calls are optional. If the key variable contains the key itself in PEM format, then it can be passed directly to OpenSSL functions. In this case, the openssl_pkey_get_private/openssl_pkey_get_public functions are called only as an example and to check that the key was specified correctly.
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084

Asymmetric Encryption​

EurekaLog offers the Для просмотра ссылки Войди или Зарегистрируйся and Для просмотра ссылки Войди или Зарегистрируйся functions, which are used in a similar way to the symmetric encryption functions above. There are only a few differences:
  1. Because asymmetric encryption uses two different keys, it is used in sender-recipient scenarios and is not used when the same person encrypts and decrypts data.
  2. Since asymmetric encryption is very slow, it is never applied to the open data itself. Instead, the open data is encrypted with any symmetric cipher with a random key (called a "session key"), and then the symmetric session key is encrypted with asymmetric encryption.
  3. The public key is used for encryption, and the private key is used for decryption. Therefore anyone can encrypt data with the recipient's public key, while only the recipient can decrypt the data. This is how secrecy is ensured.

RSAEncrypt/RSADecrypt functions work with little-endian data and use PKCS#1 Type 2 padding.

Encrypting file on a disk:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
uses
EEncrypt; // for all RSA functions and SecureFree

procedure TForm1.Button1Click(Sender: TObject);
var
// (Random) session key to encrypt the file
SessionKey: TTwofishKey;
SessionKeyRAW: TTwofishRAWKey;
// Asymmetric key to encrypt the session key
RSAKey: TRSAKey;
// Open data (file) to encrypt
Data: TMemoryStream;
// Encrypted data
EncryptedData: TEncryptBuffer;
// Stream to save the encrypted file
FS: TFileStream;
begin
// Prepare all buffers
FillChar(SessionKey, SizeOf(SessionKey), 0);
FillChar(SessionKeyRAW, SizeOf(SessionKeyRAW), 0);
FillChar(RSAKey, SizeOf(RSAKey), 0);
FillChar(EncryptedData, SizeOf(EncryptedData), 0);

try
// Create a new (random) session key
SessionKeyRAW := TwofishInitSessionKeyRAW;
SessionKey := TwofishInitKey(SessionKeyRAW);

// FS is used twice:
// 1). To store encrypted session key
// 2). To store encrypted data

FS := nil;
try

// Step 1: encrypt session key

// Load public key from a file (must be prepared beforehand)
RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
try

// Encrypt session key (SessionKeyRAW) into EncryptedData buffer
EncryptedData.cbData := SizeOf(SessionKeyRAW);
RSAEncrypt(RSAKey, @SessionKeyRAW, Pointer(EncryptedData.pbData), EncryptedData.cbData);

finally
// No longer need public key
SecureFree(RSAKey);
// No longer need source for the session key
SecureFree(SessionKeyRAW);
end;
try

// Now: EncryptedData stored encrypted session key

// Save encrypted session key into destination file
FS := TFileStream.Create('C:\Documents\EncryptedData.bin', fmCreate or fmShareExclusive);
FS.WriteBuffer(EncryptedData.cbData, SizeOf(EncryptedData.cbData));
FS.WriteBuffer(EncryptedData.pbData^, EncryptedData.cbData);

// Don't close FS yet, we are not finished saving data...

finally
// Wipe encrypted session key
SecureFree(EncryptedData);
end;

// Now we have SessionKey and destination file FS


// Step 2: encrypt the file

Data := TMemoryStream.Create;
try

// Load entire file into memory
Data.LoadFromFile('C:\Documents\Text.txt');

// Encrypt the file
// We are not using initialization vector, so ECB mode will be used
EncryptedData.cbData := Cardinal(Data.Size);
TwofishEncrypt(SessionKey, Data.Memory, Pointer(EncryptedData.pbData), EncryptedData.cbData);

// No longer needed
SecureFree(SessionKey);

// Now save encrypted file into destination file
FS.WriteBuffer(EncryptedData.pbData^, EncryptedData.cbData);

finally
// Wipe everything
SecureFree(Data);
SecureFree(EncryptedData);
end;

finally
// Close the file
FreeAndNil(FS);
end;

// Now the C:\Documents\EncryptedData.bin file is ready
// It contains encrypted (random) session key, as well as encrypted C:\Documents\Text.txt file
// This file can be descrypted by someone, who has the private key
// E.g. you can safely pass this file to the recipient by any means, including unsecured channels

finally
// Wipe the remaining data
SecureFree(SessionKey);
end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
// (Random) session key to decrypt the file
SessionKey: TTwofishKey;
// Asymmetric key to decrypt the session key
RSAKey: TRSAKey;
// Encrypted file
Data: TMemoryStream;
// Decrypted file
DecryptedData: TEncryptBuffer;
// Stream to load the encrypted file
FS: TFileStream;
begin
// Prepare all buffers
FillChar(SessionKey, SizeOf(SessionKey), 0);
FillChar(RSAKey, SizeOf(RSAKey), 0);
FillChar(DecryptedData, SizeOf(DecryptedData), 0);

// Open encrypted file
FS := TFileStream.Create('C:\Documents\EncryptedData.bin', fmOpenRead or fmShareDenyWrite);
try

try
// Step 1: decrypt the session key

// Read encrypted session key from the file
FS.ReadBuffer(DecryptedData.cbData, SizeOf(DecryptedData.cbData));
DecryptedData.pbData := AllocMem(DecryptedData.cbData);
try
FS.ReadBuffer(DecryptedData.pbData^, DecryptedData.cbData);

// Decrypt symmetric session key with private asymmetric key
RSAKey := RSALoadPrivateKey('C:\Documents\private.pem', rsPEM);
try
EEncrypt.RSADecrypt(RSAKey, DecryptedData);
finally
SecureFree(RSAKey);
end;

// Initialize the session key
Assert(DecryptedData.cbData = SizeOf(TTwofishRAWKey));
SessionKey := TwofishInitKey(TTwofishRAWKey(Pointer(DecryptedData.pbData)^));

finally
// Wipe unneeded data
SecureFree(DecryptedData);
end;

// Now we have symmetric SessionKey, which we can use to decrypt the rest of the file

// Step 2: decrypt the file

Data := TMemoryStream.Create;
try
// The remaining data in the file represents the encrypted file
Data.CopyFrom(FS, FS.Size - FS.Position);

// Decrypt the file
DecryptedData.cbData := Cardinal(Data.Size);
TwofishDecrypt(SessionKey, Data.Memory, Pointer(DecryptedData.pbData), DecryptedData.cbData);

// No longer needed
SecureFree(SessionKey);

// Save decrypted file to disk
FreeAndNil(FS);
FS := TFileStream.Create('C:\Documents\Text2.txt', fmCreate or fmShareExclusive);
try
FS.WriteBuffer(DecryptedData.pbData^, DecryptedData.cbData);
finally
FreeAndNil(FS);
end;

// No longer needed
SecureFree(DecryptedData);

// Now Text2.txt should be exact copy of Text.txt

finally
SecureFree(Data);
end;

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(SessionKey);
SecureFree(DecryptedData);
end;
finally
FreeAndNil(FS);
end;
end;
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084

Digital Signature​

When a private key is used for encryption and a public key is used for decryption - it is called a "digital signature". Anyone can decrypt the data (since everyone has the public key), therefore secrecy of data is not ensured in this way. But on the other hand, if the data can be decrypted with someone's public key, we can be sure that the data was encrypted by him (because only this person has the secret private key). This way we can check authenticity of the data.

However, as a rule, data itself is not encrypted by private key. A hash is calculated from the data and the hash is then encrypted instead. EurekaLog offers Для просмотра ссылки Войди или Зарегистрируйся (uses private key) and Для просмотра ссылки Войди или Зарегистрируйся (uses public key) functions to sign and verify. EurekaLog's digital signature functions use SHA1 with EMSA-PKCS1 padding.

The resulting digital signature is an opaque array of bytes of arbitrary length. If you want to exchange digital signatures with other environments, remember that Windows/Delphi use little-endian byte order, while some other environments (e.g. .NET or PHP) use big-endian. Therefore, in some cases, the byte order of a digital signature needs to be reversed.

For example:

Requesting software license from a PHP-script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
uses
EEncrypt, // for all RSA functions and SecureFree
EEncoding, // for Base64
EJSON, // for JSON functions
EWebTools; // for network functions

procedure TForm1.Button1Click(Sender: TObject);
var
// Data to be send to a PHP-script
JSON, JSONRequest, JSONUser: IJSONValues;
JSONText: String;
JSONRAW: RawByteString;
// (Random) symmetric key to encrypt the data
SessionKey: TTwofishKey;
SessionKeyRAW: TTwofishRAWKey;
// Asymmetric key to encrypt the session key
RSAKey: TRSAKey;
// Encrypted data (bytes)
EncryptedData: TEncryptBuffer;
// Encrypted data (text)
EncodedKey, EncodedData: String;
// URL to call PHP-script
URL: String;
// Reply from PHP (bytes)
ReplyRAW: RawByteString;
// License (text)
EncodedLicense: String;
// Digital signature (text)
EncodedSignature: String;
// License (bytes)
License: RawByteString;
// Digital signature (bytes)
Signature: RawByteString;
begin
// Prepare all buffers
FillChar(SessionKey, SizeOf(SessionKey), 0);
FillChar(SessionKeyRAW, SizeOf(SessionKeyRAW), 0);
FillChar(RSAKey, SizeOf(RSAKey), 0);
FillChar(EncryptedData, SizeOf(EncryptedData), 0);

try

// Step 1: prepare some data to send to a PHP-script

// The code below is just an example
JSON := JSONCreate;
JSONRequest := JSONCreate;
JSONUser := JSONCreate;

JSONRequest['version'] := 1;
JSONRequest['type'] := 'license';
JSONRequest['scope'] := 'installer';

JSONUser['login'] := 'input-from-edit1';
JSONUser['password'] := 'input-from-edit2';

JSON['app'] := 'MyApp';
JSON['version'] := GetModuleVersion(GetModuleName(HInstance));
JSON['date'] := Now;
JSON['request'] := JSONRequest;
JSON['user'] := JSONUser;

JSONText := JSON.ToString;
Finalize(JSONUser); // optional
Finalize(JSONRequest); // optional
Finalize(JSON); // optional
(*

Now JSONText contains:

{
"app": "MyApp",
"version": "1.0.0.0",
"date": "2021.06.25 14:04:21",
"request": {
"version": 1,
"type": "license",
"scope": "installer"
}
"user": {
"login": "input-from-edit1",
"password": "input-from-edit2"
}
}

*)
JSONRAW := UTF8Encode(JSONText);
SecureFree(JSONText);

// Now JSONRAW contains bytes to send to a PHP-script


// Step 2: encrypt request data and send encrypted data to a PHP-script

// Generate a new random key
SessionKeyRAW := TwofishInitSessionKeyRAW;
SessionKey := TwofishInitKey(SessionKeyRAW);

// Step 2a: encrypt the session key

// Load public key from a file
RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
try

// Encrypt SessionKeyRAW into EncryptedData buffer
EncryptedData.cbData := SizeOf(SessionKeyRAW);
RSAEncrypt(RSAKey, @SessionKeyRAW, Pointer(EncryptedData.pbData), EncryptedData.cbData);
try

// No longer needed
SecureFree(RSAKey);
SecureFree(SessionKeyRAW);

// Convert bytes to text
EncodedKey := Base64EncodeToString(EncryptedData.pbData, EncryptedData.cbData);

// No longer needed
SecureFree(EncryptedData);

finally
// На всякий случай (исключение) - чистим данные
SecureFree(EncryptedData);
end;

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(RSAKey);
SecureFree(SessionKeyRAW);
end;

// Now:
// - EncodedKey stores encrypted session key (as a text)
// - SessionKey stores session key (ready to use)
// - JSONRAW stores request's bytes

// Step 2b: encrypt the request

// Since we do not use initialization vector - encryption will use ECB mode
EncryptedData.cbData := Length(JSONRAW);
TwofishEncrypt(SessionKey, Pointer(JSONRAW), Pointer(EncryptedData.pbData), EncryptedData.cbData);

try
// No longer needed
SecureFree(SessionKey);

// Convert bytes to text
EncodedData := Base64EncodeToString(EncryptedData.pbData, EncryptedData.cbData);

// No longer needed
SecureFree(EncryptedData);

// Send both encrypted data and session key to the PHP-script
// URLEncode is required to escape '+' in Base-64
URL := 'Для просмотра ссылки Войди или Зарегистрируйся' + URLEncode(EncodedKey) + '&data=' + URLEncode(EncodedData);
if not InitWebTools then
RaiseLastOSError;
try
ReplyRAW := InternetGet(URL, [], []);
finally
DoneWebTools;
end;

// PHP-script does not return anything?
// (should not happen normally)
if ReplyRAW = '' then
Abort;

// Optional
SecureFree(URL);

// No longer needed
SecureFree(EncodedKey);
SecureFree(EncodedData);

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(SessionKey);
SecureFree(EncryptedData);
SecureFree(EncodedKey);
SecureFree(EncodedData);
end;

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(SessionKey);
SecureFree(SessionKeyRAW);
SecureFree(EncodedData);
SecureFree(EncodedKey);
SecureFree(JSONText);
SecureFree(JSONRAW);
end;

// Now we have ReplyRAW - bytes from the PHP-script

try
// Convert bytes to text
JSONText := UTF8ToString(ReplyRAW);

// Optional
Finalize(ReplyRAW);

// Convert JSON-text to JSON-object
JSON := JSONCreate(JSONText);

// Optional
Finalize(JSONText);

// PHP-script has returned some error?
if JSON.IndexOf('error') >= 0 then
raise Exception.Create(JSON['error']);

// Extract license and digital signature (text)
EncodedLicense := JSON['license'];
EncodedSignature := JSON['signature'];

// Convert text to bytes
License := Base64DecodeString(EncodedLicense);
Signature := Base64DecodeString(EncodedSignature);

// Load public key from a file
RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
try

// Verify the digital signature
if RSAVerify(RSAKey, Pointer(License), Length(License), Pointer(Signature), Length(Signature)) then
begin
// Digital signature is not broken
// This means that License really came from our server

// Optional
SecureFree(RSAKey);

// Just as an example
// Real application would probably have License encrypted
ShowMessage(UTF8ToString(License));
// Will show 'This is just an example license'

end
else
ShowMessage('Not signed');

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(RSAKey);
end;

finally
// Just in case (e.g. exception) - wipe everything
SecureFree(JSONText);
SecureFree(ReplyRAW);
SecureFree(EncodedSignature);
SecureFree(EncodedLicense);
SecureFree(Signature);
SecureFree(License);
end;
end;
 

ADSoft

Местный
Регистрация
1 Окт 2007
Сообщения
223
Реакции
86
Credits
1,084
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<?php

// These functions are required, because MCrypt uses zero padding instead of PKCS#5
// OpenSSL supports PKCS#5, but does not support Twofish

// PKCS#5 padding
function pkcs5_pad($text, $blocksize = 16) {
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}

// Trims PKCS#5 padding
function pkcs5_unpad($text) {
$pad = ord($text{strlen($text)-1});
if ($pad > strlen($text)) {
return false;
}
if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
return false;
}
return substr($text, 0, -1 * $pad);
}

// Private key
// Must be related to the public key, which was used in Delphi
// If you are going to use this example, you need to replace this contant
// This is just an example. Real program would store key in a file
$PrivateKey = <<<EOD
-----BEGIN PRIVATE KEY-----
MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDkIEGZvxCUNW3u
...
X+VZcAA+CUNp1xYUuep0UoeqYtfzTuKvinNo3PWAItyqtCJNcdNWDzbWRizMEPJK
N9AYlDckmF/X/1cZxWNyiltZZ542kezn
-----END PRIVATE KEY-----
EOD;

// Read arguments (encrypted session key and encrypted data)
$EncodedKey = $_GET['key'];
$EncodedData = $_GET['data'];

// Convert text to bytes
$EncryptedKey = base64_decode($EncodedKey);
$EncryptedData = base64_decode($EncodedData);

// Convert little-endian (Windows/EurekaLog/WinCrypt) to big-endian (PHP/OpenSSL)
$EncryptedKey = strrev($EncryptedKey);

// Decrypt the session key
if (!openssl_private_decrypt($EncryptedKey, $Key, $PrivateKey)) {
echo('{ "error": ' . json_encode('openssl_private_decrypt: ' . openssl_error_string()) . ' }');
die();
}

// Decrypt the request
$Data = pkcs5_unpad(mcrypt_decrypt(MCRYPT_TWOFISH, $Key, $EncryptedData, MCRYPT_MODE_ECB));

// Convert JSON-text to JSON-object
$Data = json_decode($Data, true);

// Simple checks to validate the request (just an example)
$Request = $Data['request'];
if (($Request['version'] < 1) || ($Request['version'] > 1)) {
echo('{ "error": "Unsupported request" }');
die();
}

// Are we being asked for a license?
if ($Request['type'] == 'license') {

// Validate the requester
$User = $Data['user'];
// Just an example
$OK = (($User['login'] == 'input-from-edit1') && ($User['password'] == 'input-from-edit2'));
if ($OK) {

// Somehow obtain the license for the requester
// Real app would probably send encrypted license
$License = 'This is just an example license';

// Sign the license
openssl_sign($License, $Signature, $PrivateKey, OPENSSL_ALGO_SHA1);

// Convert big-endian (PHP/OpenSSL) to little-endian (Windows/EurekaLog/WinCrypt)
$Signature = strrev($Signature);

// Convert bytes to text
$EncodedLicense = base64_encode($License);
$EncodedSignature = base64_encode($Signature);

// Return license and digital signature to a caller
echo('{ "license": ' . json_encode($EncodedLicense) . ', "signature": ' . json_encode($EncodedSignature) . ' }');

} else {
echo('{ "error": "Access Denied" }');
die();
}
}

echo('{ "error": "Unsupported request" }');