@@ -22,7 +22,7 @@ using android::content::Context;
2222using android::security::KeyPairGeneratorSpec;
2323
2424using java::io::ByteArrayInputStream;
25- using java::io::ByteArrayOutputStream ;
25+ using java::security::SecureRandom ;
2626using java::security::KeyPair;
2727using java::security::KeyPairGenerator;
2828using java::security::KeyStore;
@@ -32,7 +32,8 @@ using java::util::Calendar;
3232
3333using javax::crypto::Cipher;
3434using javax::crypto::CipherInputStream;
35- using javax::crypto::CipherOutputStream;
35+ using javax::crypto::GCMParameterSpec;
36+ using javax::crypto::SecretKeySpec;
3637using javax::security::auth::x500::X500Principal;
3738
3839namespace {
@@ -42,6 +43,10 @@ inline QString makeAlias(const QString &service, const QString &key)
4243 return service + QLatin1Char (' /' ) + key;
4344}
4445
46+ // Magic prefix identifying the hybrid RSA+AES-GCM format (v2).
47+ // Legacy entries have no prefix and are raw RSA ciphertext.
48+ const QByteArray kHybridMagic (" QKCA" , 4 );
49+
4550} // namespace
4651
4752void ReadPasswordJobPrivate::scheduledStart ()
@@ -70,20 +75,78 @@ void ReadPasswordJobPrivate::scheduledStart()
7075 return ;
7176 }
7277
73- const auto cipher = Cipher::getInstance ( QStringLiteral ( " RSA/ECB/PKCS1Padding " )) ;
78+ QByteArray plainData ;
7479
75- if (!cipher || !cipher.init (Cipher::DECRYPT_MODE , entry.getPrivateKey ())) {
76- q->emitFinishedWithError (Error::OtherError, tr (" Could not create decryption cipher" ));
77- return ;
78- }
80+ if (encryptedData.startsWith (kHybridMagic )) {
81+ // Hybrid format: kHybridMagic(4) + encKeyLen(4 BE) + RSA(AESkey) + IV(12) + AES-GCM ciphertext
82+ const int minSize = kHybridMagic .size () + 4 + 1 + 12 + 16 ;
83+ if (encryptedData.size () < minSize) {
84+ q->emitFinishedWithError (Error::OtherError, tr (" Encrypted data is too short" ));
85+ return ;
86+ }
7987
80- QByteArray plainData;
81- const CipherInputStream inputStream (ByteArrayInputStream (encryptedData), cipher);
88+ const int lenOffset = kHybridMagic .size ();
89+ const quint32 encKeyLen =
90+ (static_cast <quint32>(static_cast <unsigned char >(encryptedData[lenOffset])) << 24 )
91+ | (static_cast <quint32>(static_cast <unsigned char >(encryptedData[lenOffset + 1 ])) << 16 )
92+ | (static_cast <quint32>(static_cast <unsigned char >(encryptedData[lenOffset + 2 ])) << 8 )
93+ | (static_cast <quint32>(static_cast <unsigned char >(encryptedData[lenOffset + 3 ])));
8294
83- QString readError;
84- if (!inputStream.readAll (plainData, &readError)) {
85- q->emitFinishedWithError (Error::OtherError, tr (" Could not decrypt data: %1" ).arg (readError));
86- return ;
95+ const int dataOffset = lenOffset + 4 ;
96+ if (encryptedData.size () < dataOffset + (int )encKeyLen + 12 + 16 ) {
97+ q->emitFinishedWithError (Error::OtherError, tr (" Encrypted data is too short" ));
98+ return ;
99+ }
100+
101+ const QByteArray encryptedKey = encryptedData.mid (dataOffset, encKeyLen);
102+ const QByteArray iv = encryptedData.mid (dataOffset + encKeyLen, 12 );
103+ const QByteArray encryptedPayload = encryptedData.mid (dataOffset + encKeyLen + 12 );
104+
105+ // Decrypt the AES key with RSA
106+ const auto rsaCipher = Cipher::getInstance (QStringLiteral (" RSA/ECB/PKCS1Padding" ));
107+ if (!rsaCipher || !rsaCipher.init (Cipher::DECRYPT_MODE , entry.getPrivateKey ())) {
108+ q->emitFinishedWithError (Error::OtherError, tr (" Could not create RSA decryption cipher" ));
109+ return ;
110+ }
111+
112+ QByteArray aesKeyBytes;
113+ QString decryptError;
114+ if (!rsaCipher.doFinal (encryptedKey, aesKeyBytes, &decryptError)) {
115+ q->emitFinishedWithError (Error::OtherError,
116+ tr (" Could not decrypt AES key: %1" ).arg (decryptError));
117+ return ;
118+ }
119+
120+ // Decrypt the payload with AES-GCM
121+ const SecretKeySpec aesKey (aesKeyBytes, QStringLiteral (" AES" ));
122+ const GCMParameterSpec gcmSpec (128 , iv);
123+ const auto aesCipher = Cipher::getInstance (QStringLiteral (" AES/GCM/NoPadding" ));
124+ if (!aesCipher || !aesCipher.init (Cipher::DECRYPT_MODE , aesKey, gcmSpec)) {
125+ q->emitFinishedWithError (Error::OtherError,
126+ tr (" Could not create AES decryption cipher" ));
127+ return ;
128+ }
129+
130+ if (!aesCipher.doFinal (encryptedPayload, plainData, &decryptError)) {
131+ q->emitFinishedWithError (Error::OtherError,
132+ tr (" Could not decrypt data: %1" ).arg (decryptError));
133+ return ;
134+ }
135+ } else {
136+ // Legacy format: raw RSA-encrypted blob (only works for data <= ~245 bytes)
137+ const auto cipher = Cipher::getInstance (QStringLiteral (" RSA/ECB/PKCS1Padding" ));
138+ if (!cipher || !cipher.init (Cipher::DECRYPT_MODE , entry.getPrivateKey ())) {
139+ q->emitFinishedWithError (Error::OtherError, tr (" Could not create decryption cipher" ));
140+ return ;
141+ }
142+
143+ const CipherInputStream inputStream (ByteArrayInputStream (encryptedData), cipher);
144+ QString readError;
145+ if (!inputStream.readAll (plainData, &readError)) {
146+ q->emitFinishedWithError (Error::OtherError,
147+ tr (" Could not decrypt data: %1" ).arg (readError));
148+ return ;
149+ }
87150 }
88151
89152 mode = plainTextStore.readMode (q->key ());
@@ -157,23 +220,69 @@ void WritePasswordJobPrivate::scheduledStart()
157220 }
158221
159222 const RSAPublicKey publicKey = entry.getCertificate ().getPublicKey ();
160- const auto cipher = Cipher::getInstance (QStringLiteral (" RSA/ECB/PKCS1Padding" ));
161223
162- if (!cipher || !cipher.init (Cipher::ENCRYPT_MODE , publicKey)) {
163- q->emitFinishedWithError (Error::OtherError, tr (" Could not create encryption cipher" ));
224+ // Generate a random AES-256 key
225+ QByteArray aesKeyBytes (32 , ' \0 ' );
226+ SecureRandom secureRandom;
227+ if (!secureRandom || !secureRandom.nextBytes (aesKeyBytes)) {
228+ q->emitFinishedWithError (Error::OtherError, tr (" Could not generate AES key" ));
164229 return ;
165230 }
166231
167- ByteArrayOutputStream outputStream;
168- CipherOutputStream cipherOutputStream (outputStream, cipher);
232+ // Generate a random 12-byte IV for AES-GCM
233+ QByteArray iv (12 , ' \0 ' );
234+ if (!secureRandom.nextBytes (iv)) {
235+ q->emitFinishedWithError (Error::OtherError, tr (" Could not generate IV" ));
236+ return ;
237+ }
169238
170- if (!cipherOutputStream.write (data) || !cipherOutputStream.close ()) {
171- q->emitFinishedWithError (Error::OtherError, tr (" Could not encrypt data" ));
239+ // Encrypt the payload with AES/GCM/NoPadding
240+ const SecretKeySpec aesKey (aesKeyBytes, QStringLiteral (" AES" ));
241+ const GCMParameterSpec gcmSpec (128 , iv);
242+ const auto aesCipher = Cipher::getInstance (QStringLiteral (" AES/GCM/NoPadding" ));
243+ if (!aesCipher || !aesCipher.init (Cipher::ENCRYPT_MODE , aesKey, gcmSpec)) {
244+ q->emitFinishedWithError (Error::OtherError, tr (" Could not create AES encryption cipher" ));
172245 return ;
173246 }
174247
248+ QByteArray encryptedPayload;
249+ QString encryptError;
250+ if (!aesCipher.doFinal (data, encryptedPayload, &encryptError)) {
251+ q->emitFinishedWithError (Error::OtherError,
252+ tr (" Could not encrypt data: %1" ).arg (encryptError));
253+ return ;
254+ }
255+
256+ // Encrypt the AES key with RSA (32 bytes always fits within RSA-2048 limit)
257+ const auto rsaCipher = Cipher::getInstance (QStringLiteral (" RSA/ECB/PKCS1Padding" ));
258+ if (!rsaCipher || !rsaCipher.init (Cipher::ENCRYPT_MODE , publicKey)) {
259+ q->emitFinishedWithError (Error::OtherError, tr (" Could not create RSA encryption cipher" ));
260+ return ;
261+ }
262+
263+ QByteArray encryptedKey;
264+ if (!rsaCipher.doFinal (aesKeyBytes, encryptedKey, &encryptError)) {
265+ q->emitFinishedWithError (Error::OtherError,
266+ tr (" Could not encrypt AES key: %1" ).arg (encryptError));
267+ return ;
268+ }
269+
270+ // Assemble blob: kHybridMagic(4) + encKeyLen(4 BE) + encryptedKey + iv(12) + encryptedPayload
271+ const quint32 encKeyLen = static_cast <quint32>(encryptedKey.size ());
272+ QByteArray blob;
273+ blob.reserve (kHybridMagic .size () + 4 + encryptedKey.size () + iv.size ()
274+ + encryptedPayload.size ());
275+ blob += kHybridMagic ;
276+ blob += static_cast <char >((encKeyLen >> 24 ) & 0xFF );
277+ blob += static_cast <char >((encKeyLen >> 16 ) & 0xFF );
278+ blob += static_cast <char >((encKeyLen >> 8 ) & 0xFF );
279+ blob += static_cast <char >(encKeyLen & 0xFF );
280+ blob += encryptedKey;
281+ blob += iv;
282+ blob += encryptedPayload;
283+
175284 PlainTextStore plainTextStore (q->service (), q->settings ());
176- plainTextStore.write (q->key (), outputStream. toByteArray () , mode);
285+ plainTextStore.write (q->key (), blob , mode);
177286
178287 if (plainTextStore.error () != NoError)
179288 q->emitFinishedWithError (plainTextStore.error (), plainTextStore.errorString ());
0 commit comments