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