Skip to content

Commit 0bf268f

Browse files
Add final implementation of Crypto Module.
1 parent f5edad9 commit 0bf268f

File tree

20 files changed

+1449
-117
lines changed

20 files changed

+1449
-117
lines changed
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// Copyright 2024 PubNub Inc. All Rights Reserved.
2+
3+
4+
#include "Crypto/PubnubAesCryptor.h"
5+
#include "PubnubSubsystem.h"
6+
#include "FunctionLibraries/PubnubCryptoUtilities.h"
7+
8+
// OpenSSL includes
9+
#define UI UI_ST
10+
THIRD_PARTY_INCLUDES_START
11+
#include <openssl/evp.h>
12+
#include <openssl/rand.h>
13+
#include <openssl/sha.h>
14+
#include <openssl/err.h>
15+
THIRD_PARTY_INCLUDES_END
16+
17+
18+
TArray<uint8_t> UPubnubAesCryptor::GetIdentifier_Implementation()
19+
{
20+
return {'A', 'C', 'R', 'H'};
21+
}
22+
23+
FPubnubEncryptedData UPubnubAesCryptor::Encrypt_Implementation(const FString& Data)
24+
{
25+
if(CipherKey.IsEmpty())
26+
{
27+
UE_LOG(PubnubLog, Warning, TEXT("CipherKey is empty, can't encrypt data. Use SetCipherKey before encrypting/decrypting data"));
28+
return FPubnubEncryptedData();
29+
}
30+
31+
// Convert FString to TArray<uint8>
32+
FTCHARToUTF8 UTF8Data(*Data);
33+
TArray<uint8> DataBytes;
34+
DataBytes.Append(reinterpret_cast<const uint8*>(UTF8Data.Get()), UTF8Data.Length());
35+
36+
// Call internal encryption function
37+
FPubnubEncryptedDataInternal InternalResult;
38+
if (!EncryptData(CipherKey, DataBytes, InternalResult))
39+
{
40+
UE_LOG(PubnubLog, Error, TEXT("Internal encryption failed"));
41+
return FPubnubEncryptedData();
42+
}
43+
44+
// Convert internal result to interface format (Base64)
45+
FPubnubEncryptedData Result = ConvertToInterface(InternalResult);
46+
47+
return Result;
48+
}
49+
50+
FString UPubnubAesCryptor::Decrypt_Implementation(const FPubnubEncryptedData& Data)
51+
{
52+
if(CipherKey.IsEmpty())
53+
{
54+
UE_LOG(PubnubLog, Warning, TEXT("CipherKey is empty, can't decrypt data. Use SetCipherKey before encrypting/decrypting data"));
55+
return "";
56+
}
57+
58+
// Validate input
59+
if (Data.EncryptedData.IsEmpty() || Data.Metadata.IsEmpty())
60+
{
61+
UE_LOG(PubnubLog, Error, TEXT("Incorrect data to decrypt. Empty encrypted data or metadata"));
62+
return FString();
63+
}
64+
65+
// Convert interface format to internal format
66+
FPubnubEncryptedDataInternal InternalData = ConvertFromInterface(Data);
67+
68+
// Call internal decryption function
69+
TArray<uint8> DecryptedBytes;
70+
if (!DecryptData(CipherKey, InternalData, DecryptedBytes))
71+
{
72+
UE_LOG(PubnubLog, Error, TEXT("Internal decryption failed"));
73+
return FString();
74+
}
75+
76+
// Convert TArray<uint8> back to FString
77+
// Add null terminator for safety
78+
DecryptedBytes.Add(0);
79+
FString Result = FString(UTF8_TO_TCHAR(reinterpret_cast<const char*>(DecryptedBytes.GetData())));
80+
81+
return Result;
82+
}
83+
84+
85+
86+
bool UPubnubAesCryptor::EncryptData(const FString& CipherKey, const TArray<uint8>& DataToEncrypt, FPubnubEncryptedDataInternal& OutResult)
87+
{
88+
// Clear output
89+
OutResult.Data.Empty();
90+
OutResult.Metadata.Empty();
91+
92+
if (DataToEncrypt.Num() == 0)
93+
{
94+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: No data to encrypt"));
95+
return false;
96+
}
97+
98+
// 1. Hash the cipher key with SHA256 (same as pbsha256_digest_str)
99+
uint8 KeyHash[Sha256Len + 1]; // +1 for null terminator like in original
100+
if (!HashKeySHA256(CipherKey, KeyHash))
101+
{
102+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to hash cipher key"));
103+
return false;
104+
}
105+
KeyHash[Sha256Len] = '\0'; // Null terminate like in original
106+
107+
// 2. Generate random IV (same as generate_init_vector)
108+
uint8 IV[AesIvSize];
109+
GenerateRandomIV(IV, AesIvSize);
110+
111+
// 3. Calculate buffer size (same as estimated_enc_buffer_size)
112+
int32 EstimatedSize = DataToEncrypt.Num() + (AesBlockSize - (DataToEncrypt.Num() % AesBlockSize)) + AesBlockSize;
113+
OutResult.Data.SetNum(EstimatedSize);
114+
115+
// 4. Perform AES-256-CBC encryption (same as pbaes256_encrypt)
116+
EVP_CIPHER_CTX* Context = EVP_CIPHER_CTX_new();
117+
if (!Context)
118+
{
119+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to create encryption context"));
120+
return false;
121+
}
122+
123+
bool bSuccess = false;
124+
int32 Len = 0;
125+
int32 TotalLen = 0;
126+
127+
do
128+
{
129+
// Initialize encryption with AES-256-CBC
130+
if (EVP_EncryptInit_ex(Context, EVP_aes_256_cbc(), nullptr, KeyHash, IV) != 1)
131+
{
132+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to initialize AES-256 encryption"));
133+
break;
134+
}
135+
136+
// Encrypt the data
137+
if (EVP_EncryptUpdate(Context, OutResult.Data.GetData(), &Len, DataToEncrypt.GetData(), DataToEncrypt.Num()) != 1)
138+
{
139+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to encrypt data"));
140+
break;
141+
}
142+
TotalLen = Len;
143+
144+
// Finalize encryption (adds PKCS padding)
145+
if (EVP_EncryptFinal_ex(Context, OutResult.Data.GetData() + Len, &Len) != 1)
146+
{
147+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to finalize encryption"));
148+
break;
149+
}
150+
TotalLen += Len;
151+
152+
// Resize to actual encrypted size
153+
OutResult.Data.SetNum(TotalLen);
154+
155+
// Store IV as metadata (same as original)
156+
OutResult.Metadata.SetNum(AesIvSize);
157+
FMemory::Memcpy(OutResult.Metadata.GetData(), IV, AesIvSize);
158+
159+
bSuccess = true;
160+
161+
} while (false);
162+
163+
EVP_CIPHER_CTX_free(Context);
164+
165+
if (!bSuccess)
166+
{
167+
OutResult.Data.Empty();
168+
OutResult.Metadata.Empty();
169+
}
170+
171+
return bSuccess;
172+
}
173+
174+
bool UPubnubAesCryptor::DecryptData(const FString& CipherKey, const FPubnubEncryptedDataInternal& EncryptedData, TArray<uint8>& OutResult)
175+
{
176+
OutResult.Empty();
177+
178+
if (EncryptedData.Data.Num() == 0 || EncryptedData.Metadata.Num() != AesIvSize)
179+
{
180+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Invalid encrypted data or metadata"));
181+
return false;
182+
}
183+
184+
// Hash the cipher key
185+
uint8 KeyHash[Sha256Len + 1];
186+
if (!HashKeySHA256(CipherKey, KeyHash))
187+
{
188+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to hash cipher key for decryption"));
189+
return false;
190+
}
191+
KeyHash[Sha256Len] = '\0';
192+
193+
// Prepare output buffer
194+
int32 EstimatedSize = EncryptedData.Data.Num() + AesBlockSize + 1;
195+
OutResult.SetNum(EstimatedSize);
196+
197+
EVP_CIPHER_CTX* Context = EVP_CIPHER_CTX_new();
198+
if (!Context)
199+
{
200+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to create decryption context"));
201+
return false;
202+
}
203+
204+
bool bSuccess = false;
205+
int32 Len = 0;
206+
int32 TotalLen = 0;
207+
208+
do
209+
{
210+
// Initialize decryption
211+
if (EVP_DecryptInit_ex(Context, EVP_aes_256_cbc(), nullptr, KeyHash, EncryptedData.Metadata.GetData()) != 1)
212+
{
213+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to initialize AES-256 decryption"));
214+
break;
215+
}
216+
217+
// Decrypt the data
218+
if (EVP_DecryptUpdate(Context, OutResult.GetData(), &Len, EncryptedData.Data.GetData(), EncryptedData.Data.Num()) != 1)
219+
{
220+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to decrypt data"));
221+
break;
222+
}
223+
TotalLen = Len;
224+
225+
// Finalize decryption (removes PKCS padding)
226+
if (EVP_DecryptFinal_ex(Context, OutResult.GetData() + Len, &Len) != 1)
227+
{
228+
UE_LOG(PubnubLog, Error, TEXT("PubNubAESCrypto: Failed to finalize decryption"));
229+
break;
230+
}
231+
TotalLen += Len;
232+
233+
// Resize to actual decrypted size
234+
OutResult.SetNum(TotalLen);
235+
bSuccess = true;
236+
237+
} while (false);
238+
239+
EVP_CIPHER_CTX_free(Context);
240+
241+
if (!bSuccess)
242+
{
243+
OutResult.Empty();
244+
}
245+
246+
return bSuccess;
247+
}
248+
249+
void UPubnubAesCryptor::GenerateRandomIV(uint8* IV, int32 Size)
250+
{
251+
// Use OpenSSL's secure random number generator (same as original uses rand())
252+
// This is more secure than the original's rand() function
253+
if (RAND_bytes(IV, Size) != 1)
254+
{
255+
// Fallback to less secure method if RAND_bytes fails
256+
for (int32 i = 0; i < Size; i++)
257+
{
258+
IV[i] = FMath::RandRange(0, 255);
259+
}
260+
}
261+
}
262+
263+
bool UPubnubAesCryptor::HashKeySHA256(const FString& Key, uint8* OutHash)
264+
{
265+
// Convert FString to UTF8
266+
FTCHARToUTF8 UTF8Key(*Key);
267+
268+
// Use SHA256 (same as pbsha256_digest_str macro)
269+
if (SHA256(reinterpret_cast<const unsigned char*>(UTF8Key.Get()), UTF8Key.Length(), OutHash) == nullptr)
270+
{
271+
return false;
272+
}
273+
274+
return true;
275+
}
276+
277+
FPubnubEncryptedData UPubnubAesCryptor::ConvertToInterface(const FPubnubEncryptedDataInternal& InternalData)
278+
{
279+
FPubnubEncryptedData Result;
280+
Result.EncryptedData = UPubnubCryptoUtilities::Base64Encode(InternalData.Data);
281+
Result.Metadata = UPubnubCryptoUtilities::Base64Encode(InternalData.Metadata);
282+
return Result;
283+
}
284+
285+
FPubnubEncryptedDataInternal UPubnubAesCryptor::ConvertFromInterface(const FPubnubEncryptedData& InterfaceData)
286+
{
287+
FPubnubEncryptedDataInternal Result;
288+
UPubnubCryptoUtilities::Base64Decode(InterfaceData.EncryptedData, Result.Data);
289+
UPubnubCryptoUtilities::Base64Decode(InterfaceData.Metadata, Result.Metadata);
290+
return Result;
291+
}

0 commit comments

Comments
 (0)