-
-
Notifications
You must be signed in to change notification settings - Fork 3
fix(windows): use cryptographically random IV for AES-CBC encryption #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -121,18 +121,8 @@ | |
| let aes_provider = | ||
| SymmetricKeyAlgorithmProvider::OpenAlgorithm(&SymmetricAlgorithmNames::AesCbcPkcs7()?)?; | ||
|
|
||
| // Generate IV from domain hash | ||
| let iv_data = CryptographicBuffer::ConvertStringToBinary( | ||
| &HSTRING::from(format!("IV_{}", domain)), | ||
| BinaryStringEncoding::Utf8, | ||
| )?; | ||
| let iv_hash = hash_provider.HashData(&iv_data)?; | ||
|
|
||
| // Take first 16 bytes of IV hash for AES-128 | ||
| let mut iv_bytes: windows::core::Array<u8> = windows::core::Array::new(); | ||
| CryptographicBuffer::CopyToByteArray(&iv_hash, &mut iv_bytes)?; | ||
| let iv_slice: Vec<u8> = iv_bytes.as_slice()[..16].to_vec(); | ||
| let iv = CryptographicBuffer::CreateFromByteArray(&iv_slice)?; | ||
| // Generate a cryptographically random IV (16 bytes for AES-CBC) | ||
| let iv = CryptographicBuffer::GenerateRandom(16)?; | ||
|
|
||
| // Create symmetric key | ||
| let key = aes_provider.CreateSymmetricKey(&key_hash)?; | ||
|
|
@@ -141,8 +131,20 @@ | |
| let data_buffer = CryptographicBuffer::CreateFromByteArray(data)?; | ||
| let encrypted_buffer = CryptographicEngine::Encrypt(&key, &data_buffer, Some(&iv))?; | ||
|
|
||
| // Convert to base64 string | ||
| Ok(CryptographicBuffer::EncodeToBase64String(&encrypted_buffer)?.to_string()) | ||
| // Prepend IV to ciphertext so it can be extracted during decryption | ||
| let mut iv_bytes: windows::core::Array<u8> = windows::core::Array::new(); | ||
| CryptographicBuffer::CopyToByteArray(&iv, &mut iv_bytes)?; | ||
| let mut encrypted_bytes: windows::core::Array<u8> = windows::core::Array::new(); | ||
| CryptographicBuffer::CopyToByteArray(&encrypted_buffer, &mut encrypted_bytes)?; | ||
|
|
||
| let mut combined = Vec::with_capacity(iv_bytes.len() + encrypted_bytes.len()); | ||
| combined.extend_from_slice(iv_bytes.as_slice()); | ||
| combined.extend_from_slice(encrypted_bytes.as_slice()); | ||
|
|
||
| let combined_buffer = CryptographicBuffer::CreateFromByteArray(&combined)?; | ||
|
|
||
| // Convert to base64 string (format: base64(IV || ciphertext)) | ||
| Ok(CryptographicBuffer::EncodeToBase64String(&combined_buffer)?.to_string()) | ||
| } | ||
|
|
||
| /// Decrypt data using Windows Hello credential | ||
|
|
@@ -180,26 +182,44 @@ | |
| let aes_provider = | ||
| SymmetricKeyAlgorithmProvider::OpenAlgorithm(&SymmetricAlgorithmNames::AesCbcPkcs7()?)?; | ||
|
|
||
| // Generate IV from domain hash (same as encryption) | ||
| // Create symmetric key | ||
| let key = aes_provider.CreateSymmetricKey(&key_hash)?; | ||
|
|
||
| // Decode from base64 | ||
| let raw_buffer = | ||
| CryptographicBuffer::DecodeFromBase64String(&HSTRING::from(encrypted_data))?; | ||
| let mut raw_bytes: windows::core::Array<u8> = windows::core::Array::new(); | ||
| CryptographicBuffer::CopyToByteArray(&raw_buffer, &mut raw_bytes)?; | ||
|
|
||
| // New format: first 16 bytes are the random IV, remainder is ciphertext | ||
| if raw_bytes.len() > 16 { | ||
| let iv = CryptographicBuffer::CreateFromByteArray(&raw_bytes.as_slice()[..16])?; | ||
| let ciphertext = | ||
| CryptographicBuffer::CreateFromByteArray(&raw_bytes.as_slice()[16..])?; | ||
|
Comment on lines
+194
to
+198
|
||
|
|
||
| if let Ok(decrypted_buffer) = | ||
| CryptographicEngine::Decrypt(&key, &ciphertext, Some(&iv)) | ||
| { | ||
| let mut decrypted_bytes: windows::core::Array<u8> = windows::core::Array::new(); | ||
| CryptographicBuffer::CopyToByteArray(&decrypted_buffer, &mut decrypted_bytes)?; | ||
| return Ok(decrypted_bytes.to_vec()); | ||
| } | ||
| } | ||
|
|
||
| // Fallback: legacy deterministic IV for data encrypted before this fix | ||
| let iv_data = CryptographicBuffer::ConvertStringToBinary( | ||
| &HSTRING::from(format!("IV_{}", domain)), | ||
| BinaryStringEncoding::Utf8, | ||
| )?; | ||
| let iv_hash = hash_provider.HashData(&iv_data)?; | ||
|
|
||
| // Take first 16 bytes of IV hash for AES-128 | ||
| let mut iv_bytes: windows::core::Array<u8> = windows::core::Array::new(); | ||
| CryptographicBuffer::CopyToByteArray(&iv_hash, &mut iv_bytes)?; | ||
| let iv_slice: Vec<u8> = iv_bytes.as_slice()[..16].to_vec(); | ||
| let iv = CryptographicBuffer::CreateFromByteArray(&iv_slice)?; | ||
| let legacy_iv_slice: Vec<u8> = iv_bytes.as_slice()[..16].to_vec(); | ||
| let legacy_iv = CryptographicBuffer::CreateFromByteArray(&legacy_iv_slice)?; | ||
|
|
||
| // Create symmetric key | ||
| let key = aes_provider.CreateSymmetricKey(&key_hash)?; | ||
|
|
||
| // Decode from base64 and decrypt | ||
| let encrypted_buffer = | ||
| CryptographicBuffer::DecodeFromBase64String(&HSTRING::from(encrypted_data))?; | ||
| let decrypted_buffer = CryptographicEngine::Decrypt(&key, &encrypted_buffer, Some(&iv))?; | ||
| let decrypted_buffer = | ||
| CryptographicEngine::Decrypt(&key, &raw_buffer, Some(&legacy_iv))?; | ||
|
|
||
| // Convert to bytes | ||
| let mut decrypted_bytes: windows::core::Array<u8> = windows::core::Array::new(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new storage format is
base64(IV || ciphertext)but it does not include any version/magic marker. Indecrypt_data, this makes the format ambiguous with legacy payloads (which are also base64(ciphertext)); legacy ciphertexts that are >= 2 blocks can be misinterpreted as (IV=ciphertext[0..16], ciphertext=ciphertext[16..]) and decrypt “successfully” to truncated/garbled plaintext. Consider adding an explicit prefix (e.g., "v2:" or a short magic header before the IV) so decryption can reliably select the correct format without trial-decrypting.