|
| 1 | +--- |
| 2 | +title: Decrypt exported snapshots from Recall |
| 3 | +description: Learn how to decrypt exported snapshot data from Windows Recall. This feature is only available to users located in the European Economic Area (EEA). |
| 4 | +ms.author: mattwoj |
| 5 | +author: mattwojo |
| 6 | +ms.date: 12/01/2025 |
| 7 | +ms.topic: article |
| 8 | +no-loc: [Recall] |
| 9 | +--- |
| 10 | + |
| 11 | +# Decrypt exported snapshots from Recall |
| 12 | + |
| 13 | +This guide shows developers how to decrypt exported Recall snapshots for use in applications. You'll learn the complete decryption process with working code samples that you can implement immediately. |
| 14 | + |
| 15 | +**Exporting Recall snapshots is only supported on devices in the European Economic Area (EEA).** Export of Recall snapshots is a user-initiated process and is per user. Exported snapshots are encrypted. |
| 16 | + |
| 17 | +Learn more about how to [Export Recall snapshots](https://support.microsoft.com/windows/export-recall-snapshots-680bd134-4aaa-4bf5-8548-a8e2911c8069) or see the [Recall overview](index.md) for more about how this AI-backed feature works. |
| 18 | + |
| 19 | +## Prerequisites |
| 20 | + |
| 21 | +The option to export Recall snapshots is only available on [Copilot+ PC devices](https://www.microsoft.com/windows/copilot-plus-pcs) in the European Economic Area (EEA) that are running the latest [Windows Insider Program preview build](https://www.microsoft.com/windowsinsider). |
| 22 | + |
| 23 | +Before you begin, you'll need: |
| 24 | + |
| 25 | +- **Exported snapshots**: The user must first [export Recall snapshots](https://support.microsoft.com/windows/export-recall-snapshots-680bd134-4aaa-4bf5-8548-a8e2911c8069) and provide the folder path where they're saved. |
| 26 | +- **Export code**: The 32-character Recall export code provided during snapshot export. |
| 27 | +- **Output folder**: A destination folder path where the decrypted .jpg and .json files associated with the exported snapshots will be saved. |
| 28 | + |
| 29 | +## How to decrypt exported Recall snapshots |
| 30 | + |
| 31 | +Get started with sample code for decrypting exported Recall snapshots in the [RecallSnapshotsExport GitHub repository](https://github.com/microsoft/RecallSnapshotsExport). Follow the step-by-step process below to understand how the decryption works. |
| 32 | + |
| 33 | +### Compute Export Key |
| 34 | + |
| 35 | +The user will need to provide the location (folder path) where their exported Recall snapshots have been saved, in addition to the Recall export code that they were asked to save during the initial Recall setup. The Recall export code looks something like: `0a0a-0a0a-1111-bbbb-2222-3c3c-3c3c-3c3c` |
| 36 | + |
| 37 | +First, remove the dash – to result in a 32-character string: `0a0a0a0a1111bbbb22223c3c3c3c3c3c` |
| 38 | + |
| 39 | +```cpp |
| 40 | +std::wstring UnexpandExportCode(std::wstring code) |
| 41 | +{ |
| 42 | + if (code.size() > 32) |
| 43 | + { |
| 44 | + code.erase(std::remove(code.begin(), code.end(), ' '), code.end()); // Remove spaces |
| 45 | + code.erase(std::remove(code.begin(), code.end(), '-'), code.end()); // Remove hyphens |
| 46 | + } |
| 47 | + |
| 48 | + |
| 49 | + if (code.size() != 32) |
| 50 | + { |
| 51 | + std::wcout << L"The export code has incorrect number of characters."<< std::endl; |
| 52 | + } |
| 53 | + |
| 54 | + |
| 55 | + return code; |
| 56 | +} |
| 57 | +``` |
| 58 | +
|
| 59 | +[See sample code](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L307-L321) |
| 60 | +
|
| 61 | +Next, build an array containing the byte value for each pair of hex digits in turn. |
| 62 | +
|
| 63 | +```cpp |
| 64 | +std::vector<uint8_t> HexStringToBytes(const std::wstring& hexString) |
| 65 | +{ |
| 66 | + std::vector<uint8_t> bytes; |
| 67 | + if (hexString.length() % 2 != 0) |
| 68 | + { |
| 69 | + throw std::invalid_argument("Hex string must have an even length"); |
| 70 | + } |
| 71 | +
|
| 72 | +
|
| 73 | + for (size_t i = 0; i < hexString.length(); i += 2) |
| 74 | + { |
| 75 | + std::wstring byteString = hexString.substr(i, 2); |
| 76 | + uint8_t byte = static_cast<uint8_t>(std::stoi(byteString, nullptr, 16)); |
| 77 | + bytes.push_back(byte); |
| 78 | + } |
| 79 | +
|
| 80 | +
|
| 81 | + return bytes; |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +[See sample code](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L171-L187) |
| 86 | + |
| 87 | +Then, take that array and compute the SHA256 hash, which results in a 32-byte value, which is the export key. Now any number of snapshots can be decrypted using the resulting export key. |
| 88 | + |
| 89 | +```cpp |
| 90 | + std::vector<uint8_t> exportKeyBytes(c_keySizeInBytes); |
| 91 | + THROW_IF_NTSTATUS_FAILED(BCryptHash( |
| 92 | + BCRYPT_SHA256_ALG_HANDLE, |
| 93 | + nullptr, |
| 94 | + 0, |
| 95 | + exportCodeBytes.data(), |
| 96 | + static_cast<ULONG>(exportCodeBytes.size()), |
| 97 | + exportKeyBytes.data(), |
| 98 | + c_keySizeInBytes)); |
| 99 | +``` |
| 100 | +
|
| 101 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L200-L208) |
| 102 | +
|
| 103 | +### Decrypt the encrypted snapshots |
| 104 | +
|
| 105 | +The layout of a snapshot (in little-endian format): `| uint32_t version | uint32_t encryptedKeySize | uint32_t encryptedContentSize | uint32_t contentType | uint8_t[KeySIze] encryptedContentKey | uint8_t[ContentSize] encryptedContent |` |
| 106 | +
|
| 107 | +First, read the four [uint32_t values](/cpp/c-runtime-library/standard-types#fixed-width-integral-types-stdinth). |
| 108 | +
|
| 109 | +```cpp |
| 110 | + EncryptedSnapshotHeader header{}; |
| 111 | + reader.ByteOrder(winrt::ByteOrder::LittleEndian); |
| 112 | +
|
| 113 | +
|
| 114 | + header.Version = reader.ReadUInt32(); |
| 115 | + header.KeySize = reader.ReadUInt32(); |
| 116 | + header.ContentSize = reader.ReadUInt32(); |
| 117 | + header.ContentType = reader.ReadUInt32(); |
| 118 | +``` |
| 119 | + |
| 120 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L222-L228) |
| 121 | + |
| 122 | +Next, verify that version has the value, 2. |
| 123 | + |
| 124 | +```cpp |
| 125 | + if (header.Version != 2) |
| 126 | + { |
| 127 | + throw std::runtime_error("Insufficient data header version."); |
| 128 | + } |
| 129 | +``` |
| 130 | +
|
| 131 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L230-L233) |
| 132 | +
|
| 133 | +Then, read the encryptedKeyContent. |
| 134 | +
|
| 135 | +```cpp |
| 136 | + std::vector<uint8_t> keybytes(header.KeySize); |
| 137 | + reader.ReadBytes(keybytes); |
| 138 | +``` |
| 139 | + |
| 140 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L235-L236) |
| 141 | + |
| 142 | +Decrypt the encryptedKeyContent |
| 143 | + |
| 144 | +```cpp |
| 145 | +wil::unique_bcrypt_key DecryptExportKey(BCRYPT_KEY_HANDLE key, std::span<uint8_t const> encryptedKey) |
| 146 | +{ |
| 147 | + THROW_HR_IF(E_INVALIDARG, encryptedKey.size() != c_totalSizeInBytes); |
| 148 | + |
| 149 | + |
| 150 | + BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO AuthInfo{}; |
| 151 | + BCRYPT_INIT_AUTH_MODE_INFO(AuthInfo); |
| 152 | + AuthInfo.pbNonce = const_cast<uint8_t*>(encryptedKey.data()); |
| 153 | + AuthInfo.cbNonce = c_nonceSizeInBytes; |
| 154 | + AuthInfo.pbTag = const_cast<uint8_t*>(encryptedKey.data() + c_nonceSizeInBytes + c_childKeySizeInBytes); |
| 155 | + AuthInfo.cbTag = c_tagSizeInBytes; |
| 156 | + |
| 157 | + |
| 158 | + uint8_t decryptedKey[c_childKeySizeInBytes] = { 0 }; |
| 159 | + |
| 160 | + |
| 161 | + ULONG decryptedByteCount{}; |
| 162 | + THROW_IF_FAILED(HResultFromBCryptStatus(BCryptDecrypt( |
| 163 | + key, |
| 164 | + const_cast<uint8_t*>(encryptedKey.data() + c_nonceSizeInBytes), |
| 165 | + c_childKeySizeInBytes, |
| 166 | + &AuthInfo, |
| 167 | + nullptr, |
| 168 | + 0, |
| 169 | + decryptedKey, |
| 170 | + sizeof(decryptedKey), |
| 171 | + &decryptedByteCount, |
| 172 | + 0))); |
| 173 | + |
| 174 | + |
| 175 | + wil::unique_bcrypt_key childKey; |
| 176 | + THROW_IF_NTSTATUS_FAILED( |
| 177 | + BCryptGenerateSymmetricKey(BCRYPT_AES_GCM_ALG_HANDLE, &childKey, nullptr, 0, decryptedKey, c_childKeySizeInBytes, 0)); |
| 178 | + |
| 179 | + |
| 180 | + return childKey; |
| 181 | +} |
| 182 | +``` |
| 183 | +
|
| 184 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L138-L169) |
| 185 | +
|
| 186 | +using the exportKey |
| 187 | +
|
| 188 | +```cpp |
| 189 | + wil::unique_bcrypt_key exportKey; |
| 190 | + THROW_IF_NTSTATUS_FAILED(BCryptGenerateSymmetricKey( |
| 191 | + BCRYPT_AES_GCM_ALG_HANDLE, &exportKey, nullptr, 0, exportKeyBytes.data(), static_cast<ULONG>(exportKeyBytes.size()), 0)); |
| 192 | +``` |
| 193 | + |
| 194 | +[See sample code](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L210-L212) |
| 195 | + |
| 196 | +To get the contentKey (crypto algorithm is AES_GCM) |
| 197 | + |
| 198 | +```cpp |
| 199 | + wil::unique_bcrypt_key contentKey = DecryptExportKey(exportKey.get(), keybytes); |
| 200 | +``` |
| 201 | + |
| 202 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L237) |
| 203 | + |
| 204 | +Read the encryptedContent |
| 205 | + |
| 206 | +```cpp |
| 207 | + std::vector<uint8_t> contentbytes(header.ContentSize); |
| 208 | + reader.ReadBytes(contentbytes); |
| 209 | +``` |
| 210 | +
|
| 211 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L239-L240) |
| 212 | +
|
| 213 | +Decrypt the encryptedContent |
| 214 | +
|
| 215 | +```cpp |
| 216 | +std::vector<uint8_t> DecryptPackedData(BCRYPT_KEY_HANDLE key, std::span<uint8_t const> payload) |
| 217 | +{ |
| 218 | + THROW_HR_IF(E_INVALIDARG, payload.size() < c_tagSizeInBytes); |
| 219 | + const auto dataSize = payload.size() - c_tagSizeInBytes; |
| 220 | + const auto data = payload.data(); |
| 221 | +
|
| 222 | +
|
| 223 | + uint8_t zeroNonce[c_nonceSizeInBytes] = { 0 }; |
| 224 | + BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo{}; |
| 225 | + BCRYPT_INIT_AUTH_MODE_INFO(authInfo); |
| 226 | + authInfo.pbNonce = zeroNonce; |
| 227 | + authInfo.cbNonce = c_nonceSizeInBytes; |
| 228 | + authInfo.pbTag = const_cast<uint8_t*>(payload.data() + dataSize); |
| 229 | + authInfo.cbTag = c_tagSizeInBytes; |
| 230 | +
|
| 231 | +
|
| 232 | + std::vector<uint8_t> decryptedContent(dataSize); |
| 233 | + ULONG decryptedSize = 0; |
| 234 | + const auto result = BCryptDecrypt( |
| 235 | + key, const_cast<uint8_t*>(data), static_cast<ULONG>(dataSize), &authInfo, nullptr, 0, decryptedContent.data(), static_cast<ULONG>(dataSize), &decryptedSize, 0); |
| 236 | + decryptedContent.resize(decryptedSize); |
| 237 | +
|
| 238 | +
|
| 239 | + THROW_IF_FAILED(HResultFromBCryptStatus(result)); |
| 240 | +
|
| 241 | +
|
| 242 | + return decryptedContent; |
| 243 | +} |
| 244 | +``` |
| 245 | + |
| 246 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L113-L136) |
| 247 | + |
| 248 | +with the contentKey (crypto algorithm is AES_GCM) |
| 249 | + |
| 250 | +```cpp |
| 251 | + std::vector<uint8_t> decryptedContent = DecryptPackedData(contentKey.get(), contentbytes); |
| 252 | +``` |
| 253 | + |
| 254 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L241) |
| 255 | + |
| 256 | +Output decrypted Recall snapshot content in the form of a .jpg image with corresponding .json metadata into the designated folder path |
| 257 | + |
| 258 | +```cpp |
| 259 | +void WriteSnapshotToOutputFolder(winrt::StorageFolder const& outputFolder, winrt::hstring const& fileName, winrt::IRandomAccessStream const& decryptedStream) |
| 260 | +``` |
| 261 | +
|
| 262 | +[See sample code.](https://github.com/microsoft/RecallSnapshotsExport/blob/5442b3a90aed91888a67c283c3132290ecddb044/RecallSnapshotsExport.cpp#L251) |
| 263 | +
|
| 264 | +The expected output will include: |
| 265 | +
|
| 266 | +- Decrypted snapshots saved as .jpg files. |
| 267 | +- Corresponding metadata saved as .json files. |
| 268 | +
|
| 269 | +Both file types will share the same filename and be found in the specified output folder. |
| 270 | +
|
| 271 | +## Learn more about Recall |
| 272 | +
|
| 273 | +- [Learn more about Windows Recall](index.md). |
| 274 | +- [Export Recall snapshots with an export code](https://support.microsoft.com/topic/680bd134-4aaa-4bf5-8548-a8e2911c8069) |
| 275 | +- [Manage Recall: Allow export of Recall and snapshot information](/windows/client-management/manage-recall#allow-export-of-recall-and-snapshot-information): IT Administrator guidance on how to manage Recall settings for users within your company, including the ability to export Recall snapshot data. |
| 276 | +- [Configuration Service Provider (CSP) Policy for Windows AI: Allow Recall Export](/windows/client-management/mdm/policy-csp-windowsai#allowrecallexport): Guidance for IT administrators to establish the policy settings that determine whether the optional Recall feature is available for end users to enable on their device, including the policy for enabling the export of snapshot data. |
0 commit comments