Minimal object containing this commit
Commit Diff
commit 7a41f2c735aae98744c88560869bf32f5c4e6629760b59be41c5c1cbdb324c89
Author: yihanwu1024 <yihanwu1024>
Date: Tue Jun 23 21:24:08 2026 +0000
revise Chromium browser data migration on Windows to support app-bound encryption
diff --git a/0dd0b00991b62df340f5cdfb40c6a90e6e43ff2749a363a9ae924f9a4868867f b/0dd0b00991b62df340f5cdfb40c6a90e6e43ff2749a363a9ae924f9a4868867f
index 3d999c8..d6e2a1f 100644
--- a/0dd0b00991b62df340f5cdfb40c6a90e6e43ff2749a363a9ae924f9a4868867f
+++ b/0dd0b00991b62df340f5cdfb40c6a90e6e43ff2749a363a9ae924f9a4868867f
@@ -2,5 +2,4 @@
<section xmlns="http://docbook.org/ns/docbook" xml:id="0dd0b00991b62df340f5cdfb40c6a90e6e43ff2749a363a9ae924f9a4868867f">
<title>Edit <literal>Local State</literal> with new <literal>encrypted_key</literal> value</title>
<para>Now you can close Chromium, copy your data, and change the <literal>encrypted_key</literal> value to what you got in the previous step.</para>
-<para>Chromium migration is complete.</para>
</section>
diff --git a/1e73bf4502a9de1eee0055fa410b5649c8adc9b2bf07b782f730b0c7f59e635a b/1e73bf4502a9de1eee0055fa410b5649c8adc9b2bf07b782f730b0c7f59e635a
index 02b68a5..3bf27b5 100644
--- a/1e73bf4502a9de1eee0055fa410b5649c8adc9b2bf07b782f730b0c7f59e635a
+++ b/1e73bf4502a9de1eee0055fa410b5649c8adc9b2bf07b782f730b0c7f59e635a
@@ -2,6 +2,6 @@
<section xmlns="http://docbook.org/ns/docbook" xml:id="1e73bf4502a9de1eee0055fa410b5649c8adc9b2bf07b782f730b0c7f59e635a">
<title>Transform the <literal>encrypted_key</literal> value</title>
<para>Your <literal>encrypted_key</literal> value is in base64 and has an extra header inside.
-Turn it into a raw byte array and remove the header with</para>
+Turn it into a raw byte array and remove the header (length 5 or 4) with</para>
<programlisting>$encrypted_key_data = [Convert]::FromBase64String($encrypted_key)[5..9999]</programlisting>
</section>
diff --git a/3123d5e4feb6b716fa9131aabbb6f20fa748475d9d9c4a50be48cc167b00d12e b/3123d5e4feb6b716fa9131aabbb6f20fa748475d9d9c4a50be48cc167b00d12e
index f24efd5..3ccd290 100644
--- a/3123d5e4feb6b716fa9131aabbb6f20fa748475d9d9c4a50be48cc167b00d12e
+++ b/3123d5e4feb6b716fa9131aabbb6f20fa748475d9d9c4a50be48cc167b00d12e
@@ -2,7 +2,7 @@
<section xmlns="http://docbook.org/ns/docbook" xml:id="3123d5e4feb6b716fa9131aabbb6f20fa748475d9d9c4a50be48cc167b00d12e">
<title>Get the <literal>encrypted_key</literal> value from Chromium <literal>Local State</literal> file</title>
<para>Navigate to your Chromium user data directory.
-For example, Chrome is at <literal>%USERPROFILE%\AppData\Local\Google\Chrome\User Data\</literal>.
+For example, Chrome is at <literal>%LOCALAPPDATA%\Google\Chrome\User Data\</literal>.
Open <literal>Local State</literal> with a text editor.
This file is a giant JSON.
Search for the <literal>encrypted_key</literal> key, and copy down its value (from after the colon and double quote to before the next double quote.)</para>
diff --git a/37434c01fc3e2a32bd83a1b5d632ef9919bfe323dd7b38914e5d044d0eec88d5 b/37434c01fc3e2a32bd83a1b5d632ef9919bfe323dd7b38914e5d044d0eec88d5
index c3a4454..39b8021 100644
--- a/37434c01fc3e2a32bd83a1b5d632ef9919bfe323dd7b38914e5d044d0eec88d5
+++ b/37434c01fc3e2a32bd83a1b5d632ef9919bfe323dd7b38914e5d044d0eec88d5
@@ -1,9 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<section xmlns="http://docbook.org/ns/docbook" xml:id="37434c01fc3e2a32bd83a1b5d632ef9919bfe323dd7b38914e5d044d0eec88d5">
-<title>TL; DR; Copy; Paste</title>
-<para>You still need to import <literal>ProtectedData</literal>.
-Then, in your Chromium User Data directory (where there are <literal>Local State</literal> and <literal>Local State Unprotected Key</literal> files created in the previous step):</para>
-<programlisting>$unprotected_key = [byte[]] (Get-Content '.\Local State Unprotected Key'); $encrypted_key_data = [System.Security.Cryptography.ProtectedData]::Protect($unprotected_key, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser); $encrypted_key_data = [byte]68,80,65,80,73 + $encrypted_key_data; $encrypted_key = [Convert]::ToBase64String($encrypted_key_data); $json = (Get-Content '.\Local State' -Encoding UTF8 | ConvertFrom-Json); $json.os_crypt.encrypted_key = $encrypted_key; ConvertTo-Json -InputObject $json -Depth 8 -Compress | Out-File 'Local State' -Encoding utf8</programlisting>
-<para>You might want to delete the migration file <literal>Local State Unprotected Key</literal>.</para>
-<para>The individual steps are below.</para>
+<title>Encryption</title>
+<para>You still need to import <literal>System.Security</literal>.
+Then, in your Chromium User Data directory (where there are <literal>Local State</literal> and decrypted keys):</para>
+<para>As user, encrypt legacy key:</para>
+<programlisting>$unprotected_key = [byte[]] (Get-Content '.\Local State.os_crypt.encrypted_key.unprotected');
+$encrypted_key_data = [System.Security.Cryptography.ProtectedData]::Protect($unprotected_key, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser);
+$encrypted_key_data = [byte]68,80,65,80,73 + $encrypted_key_data;
+$encrypted_key = [Convert]::ToBase64String($encrypted_key_data);
+$json = (Get-Content '.\Local State' -Encoding UTF8 | ConvertFrom-Json);
+$json.os_crypt.encrypted_key = $encrypted_key;
+ConvertTo-Json -InputObject $json -Depth 8 -Compress | Out-File 'Local State' -Encoding utf8
+</programlisting>
+<para>As user, encrypt app-bound key:</para>
+<programlisting>$app_bound_unprotected_key = [byte[]] (Get-Content '.\Local State.os_crypt.app_bound_encrypted_key.unprotected')
+[System.Security.Cryptography.ProtectedData]::Protect($app_bound_unprotected_key, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) | Out-File '.\Local State.os_crypt.app_bound_encrypted_key.intermediate'</programlisting>
+<para>As <literal>SYSTEM</literal> user, encrypt app-bound key:</para>
+<programlisting>$app_bound_intermediate = [byte[]] (Get-Content '.\Local State.os_crypt.app_bound_encrypted_key.intermediate');
+$app_bound_encrypted_key_data = [System.Security.Cryptography.ProtectedData]::protect($app_bound_intermediate, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser);
+$app_bound_encrypted_key_data = [byte]65,80,80,66 + $app_bound_encrypted_key_data;
+$app_bound_encrypted_key = [Convert]::ToBase64String($app_bound_encrypted_key_data);
+$json = (Get-Content '.\Local State' -Encoding UTF8 | ConvertFrom-Json);
+$json.os_crypt.app_bound_encrypted_key = $app_bound_encrypted_key;
+ConvertTo-Json -InputObject $json -Depth 8 -Compress | Out-File 'Local State' -Encoding utf8
+Remove-Item '.\Local State.os_crypt.app_bound_encrypted_key.intermediate'</programlisting>
</section>
diff --git a/ab4ce424e032db1cbf19b4fa86588bf76da1268e78ff02ab88dc53490d93f06a b/ab4ce424e032db1cbf19b4fa86588bf76da1268e78ff02ab88dc53490d93f06a
index 812c2e4..b154ab4 100644
--- a/ab4ce424e032db1cbf19b4fa86588bf76da1268e78ff02ab88dc53490d93f06a
+++ b/ab4ce424e032db1cbf19b4fa86588bf76da1268e78ff02ab88dc53490d93f06a
@@ -4,6 +4,7 @@
<para>Now the data is ready to be processed with DPAPI.
Do this:</para>
<programlisting>$unprotected_key = [System.Security.Cryptography.ProtectedData]::Unprotect($encrypted_key_data, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)</programlisting>
-<para>It should be 128 bits (32 bytes) as of Chromium 107.</para>
-<para>Somehow note down the value of<literal>$unprotected_key</literal>.</para>
+<para>It should be 128 bits (32 bytes) for legacy encryption.
+For app-bound encryption, it will be longer.</para>
+<para>Somehow note down the value of <literal>$unprotected_key</literal>.</para>
</section>
diff --git a/af4fe462c85e030cac8c15a33b77f9b9894ec332999b2bf7afbd1f1862949ee2 b/af4fe462c85e030cac8c15a33b77f9b9894ec332999b2bf7afbd1f1862949ee2
index 2d27777..66d8e1e 100644
--- a/af4fe462c85e030cac8c15a33b77f9b9894ec332999b2bf7afbd1f1862949ee2
+++ b/af4fe462c85e030cac8c15a33b77f9b9894ec332999b2bf7afbd1f1862949ee2
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<section xmlns="http://docbook.org/ns/docbook" xml:id="af4fe462c85e030cac8c15a33b77f9b9894ec332999b2bf7afbd1f1862949ee2">
-<title>Import <literal>ProtectedData</literal> to PowerShell</title>
+<title>Import <literal>System.Security</literal> to PowerShell</title>
<para>Yeah, you have to do it again.</para>
</section>
diff --git a/c277e9b50383e6cfd0791518bb1b271be702febdb9eee1144751b03203f8d83b b/c277e9b50383e6cfd0791518bb1b271be702febdb9eee1144751b03203f8d83b
index 8137f23..d904296 100644
--- a/c277e9b50383e6cfd0791518bb1b271be702febdb9eee1144751b03203f8d83b
+++ b/c277e9b50383e6cfd0791518bb1b271be702febdb9eee1144751b03203f8d83b
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<section xmlns="http://docbook.org/ns/docbook" xml:id="c277e9b50383e6cfd0791518bb1b271be702febdb9eee1144751b03203f8d83b">
<title>On the target computer</title>
-<include xmlns="http://www.w3.org/2001/XInclude" href="37434c01fc3e2a32bd83a1b5d632ef9919bfe323dd7b38914e5d044d0eec88d5"/>
<include xmlns="http://www.w3.org/2001/XInclude" href="af4fe462c85e030cac8c15a33b77f9b9894ec332999b2bf7afbd1f1862949ee2"/>
<include xmlns="http://www.w3.org/2001/XInclude" href="bdf7c009a1bb58620061b5aeb8aa4ea5b11eab76497a379eda978546628e9631"/>
<include xmlns="http://www.w3.org/2001/XInclude" href="cc494678e68ffeffbdd41b10d47e24f0bd8dd40d03b05de48f5e633ea0b70a33"/>
diff --git a/c29e58a3d7f243fd0987926ea625adfd04bd58253b96b2ae6f197191510a26b2 b/c29e58a3d7f243fd0987926ea625adfd04bd58253b96b2ae6f197191510a26b2
index 613791c..5070f54 100644
--- a/c29e58a3d7f243fd0987926ea625adfd04bd58253b96b2ae6f197191510a26b2
+++ b/c29e58a3d7f243fd0987926ea625adfd04bd58253b96b2ae6f197191510a26b2
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<section xmlns="http://docbook.org/ns/docbook" xml:id="c29e58a3d7f243fd0987926ea625adfd04bd58253b96b2ae6f197191510a26b2">
<title>On the source computer</title>
-<include xmlns="http://www.w3.org/2001/XInclude" href="dc775bf57f268e573a34292ffc21ae6f67e49871b4354c2895684bd05cfc91a1"/>
<include xmlns="http://www.w3.org/2001/XInclude" href="cb2e2322bf0be91012f68c13938576af51b2d0542ede39c9dd166653be7f57a3"/>
<include xmlns="http://www.w3.org/2001/XInclude" href="3123d5e4feb6b716fa9131aabbb6f20fa748475d9d9c4a50be48cc167b00d12e"/>
<include xmlns="http://www.w3.org/2001/XInclude" href="1e73bf4502a9de1eee0055fa410b5649c8adc9b2bf07b782f730b0c7f59e635a"/>
diff --git a/c8d4ce49e7b92556278b2c33d05bfbe70a401def0ec93d1b585fe2d18fe4ea82 b/c8d4ce49e7b92556278b2c33d05bfbe70a401def0ec93d1b585fe2d18fe4ea82
index b002364..27fc27c 100644
--- a/c8d4ce49e7b92556278b2c33d05bfbe70a401def0ec93d1b585fe2d18fe4ea82
+++ b/c8d4ce49e7b92556278b2c33d05bfbe70a401def0ec93d1b585fe2d18fe4ea82
@@ -3,11 +3,49 @@
<title>Chromium Browser Data Migration on Windows</title>
<para>Modern Chromium utilizes the Windows Data Protection API (DPAPI), specifically <literal>CryptProtectData()</literal>.
Data migration cannot be done by simply copying the data directory to another computer, because the data would be encrypted with a key only available on the source computer.
-Fortunately, it turns out to be fairly simple to migrate Chromium user data, thanks to the design that Chromium envelops the encryption mechanism by one layer.
-The function <literal>CryptProtectData()</literal> does not encrypt the data directly, but rather a standalone AES key in the <literal>Local State</literal> file, and this key is in turn used to encrypt confidential data such as cookies and website credentials.
-Therefore, one easy way to migrate Chromium is to decrypt this Local State key and reencrypt it within the new environment.
+Fortunately, it turns out to be fairly simple to migrate Chromium user data, thanks to a design that wraps the encryption mechanism by a layer of master keys.
+The function <literal>CryptProtectData()</literal> does not encrypt the data directly, but rather master AES keys in the <literal>Local State</literal> file, and these keys are in turn used to encrypt confidential data such as cookies and website credentials.
+Therefore, one easy way to migrate Chromium is to decrypt these keys and reencrypt them within the new environment.
The process is simple enough to perform manually.</para>
+<para>Before Chromium 127, only one call to <literal>CryptProtectData()</literal> as the current user was made.
+As a result, any program running as the current user could call this API to decrypt all Chromium data.
+In Chromium 127, it added the so-called “app-bound” encryption scheme, where two calls to <literal>CryptProtectData()</literal> were made:
+once as the current user, and another as the <literal>SYSTEM</literal> user, which requires administrator privileges.
+Because the Chromium browser does not run as <literal>SYSTEM</literal> or ask for administrator privileges, communication is needed between Chromium and a service process in <literal>SYSTEM</literal>.
+This service process enforces that any communication must come from a Chromium installation in its expected location, for example <literal>C:\Program Files\Google\Chrome</literal>.
+This directory is protected by administrator privileges, which is granted only during Chromium installation.
+Therefore, non-administrator processes will not be able to decrypt Chromium data.
+However, similarly situated programs (that have access to the <literal>SYSTEM</literal> user) will still be able to decrypt Chromium data.
+If Chromium does not have access to the privileged service, it will not perform app-bound encryption.</para>
+<para>In <emphasis>Chrome</emphasis> (not Chromium) 137, Google introduced an additional step of encryption that impacts migration.
+This addition is still part of the app-bound encryption system, and internally represented with a mode flag <literal>3</literal>.
+(Other existing flags (<literal>1</literal> and <literal>2</literal>) do not affect migration beyond the previous paragraph.)
+In this mode <literal>3</literal>, an additional step of calling a Windows CNG API is performed on the DPAPI-unprotected app-bound key data (the innermost layer).
+The API call is an encrypt/decrypt oracle backed by AES (AES-256-CBC with zero IV, irrelevant to migration), and Windows prohibits exporting this key through the CNG API, per Chrome request.
+Mode <literal>3</literal> does not fundamentally change the security situation: programs with access to the <literal>SYSTEM</literal> user will still be able to use this CNG oracle.
+For migration, this app-bound key data must be decrypted and reencrypted as well, by another program.
+Not the whole app-bound key data is encrypted; only the 32 bytes after the flag <literal>3</literal> is encrypted, which is the only data you need to decrypt/reencrypt.
+You can read more about the structure <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://github.com/runassu/chrome_v20_decryption">here</link>.
+There is also <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://github.com/The-Viper-One/Invoke-PowerChrome">a PowerShell implementation</link>, if you would like to use the Windows CNG API within PowerShell.</para>
+<para>It is even possible to recover a Chromium installation to working state from an unbootable Windows installation if the required cryptographic data are present, which is generally discussed in Windows DPAPI forensics.</para>
+<section>
+<title>Implementation</title>
+<para>It is possible to perform the entire Chromium migration process in PowerShell, including Chrome’s flag <literal>3</literal> key.
+However, the case for flag <literal>3</literal> is omitted here, and you can refer to <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://github.com/The-Viper-One/Invoke-PowerChrome">a PowerShell implementation</link> for ways to call the CNG API.
+As discussed, this should be performed after DPAPI decryption and before DPAPI reencryption.</para>
+<include xmlns="http://www.w3.org/2001/XInclude" href="dc775bf57f268e573a34292ffc21ae6f67e49871b4354c2895684bd05cfc91a1"/>
+<include xmlns="http://www.w3.org/2001/XInclude" href="37434c01fc3e2a32bd83a1b5d632ef9919bfe323dd7b38914e5d044d0eec88d5"/>
+</section>
+<section>
+<title>Step-by-step Explaination</title>
+<para>A step-by-step explanation is given for one layer of DPAPI decryption/encryption.
+You can follow it manually.
+For app-bound encryption, you use the process in the user and <literal>SYSTEM</literal> user contexts.
+The inner encryption is in the user context and the outer encryption is in the <literal>SYSTEM</literal> user context.
+Legacy encryption data has a header of <literal>DPAPI</literal> and app-bound encryption data has a header of <literal>APPB</literal>, encoded in byte arrays in my implementation.
+The header wraps the completely encrypted blob once.</para>
<include xmlns="http://www.w3.org/2001/XInclude" href="c29e58a3d7f243fd0987926ea625adfd04bd58253b96b2ae6f197191510a26b2"/>
<include xmlns="http://www.w3.org/2001/XInclude" href="c277e9b50383e6cfd0791518bb1b271be702febdb9eee1144751b03203f8d83b"/>
+</section>
<include xmlns="http://www.w3.org/2001/XInclude" href="cfa9cc64db910311cd27b0129f0155d58d0a278b46231cd3d409dda99793e74f"/>
</article>
diff --git a/cb2e2322bf0be91012f68c13938576af51b2d0542ede39c9dd166653be7f57a3 b/cb2e2322bf0be91012f68c13938576af51b2d0542ede39c9dd166653be7f57a3
index d273766..2298bf7 100644
--- a/cb2e2322bf0be91012f68c13938576af51b2d0542ede39c9dd166653be7f57a3
+++ b/cb2e2322bf0be91012f68c13938576af51b2d0542ede39c9dd166653be7f57a3
@@ -1,12 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<section xmlns="http://docbook.org/ns/docbook" xml:id="cb2e2322bf0be91012f68c13938576af51b2d0542ede39c9dd166653be7f57a3">
-<title>Import <literal>ProtectedData</literal> to PowerShell</title>
+<title>Import <literal>System.Security</literal> to PowerShell</title>
<para><literal>System.Security.Cryptography.ProtectedData</literal> is a Microsoft .NET Core library that handles DPAPI calls.
-You can <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://www.nuget.org/packages/System.Security.Cryptography.ProtectedData/4.7.0">download it from NuGet</link>.
-The following procedure has been tested with version 4.6.1 of the library.</para>
-<para>.nupkg packages are zip files.
-Change the extension and extract a <literal>System.Security.Cryptography.ProtectedData.dll</literal> file.</para>
-<para>Then, open a Windows PowerShell window and use the following command to import the library to the current session.
-You may need to change your PowerShell execution policy and unblock the downloaded file for this step.</para>
-<programlisting>Add-Type -Path <Path to library></programlisting>
+It is built into Windows and you can import it with the following command:</para>
+<programlisting>Add-Type -AssemblyName System.Security</programlisting>
</section>
diff --git a/cc494678e68ffeffbdd41b10d47e24f0bd8dd40d03b05de48f5e633ea0b70a33 b/cc494678e68ffeffbdd41b10d47e24f0bd8dd40d03b05de48f5e633ea0b70a33
index 56b1a01..b47e177 100644
--- a/cc494678e68ffeffbdd41b10d47e24f0bd8dd40d03b05de48f5e633ea0b70a33
+++ b/cc494678e68ffeffbdd41b10d47e24f0bd8dd40d03b05de48f5e633ea0b70a33
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<section xmlns="http://docbook.org/ns/docbook" xml:id="cc494678e68ffeffbdd41b10d47e24f0bd8dd40d03b05de48f5e633ea0b70a33">
<title>Transform to an <literal>encrypted_key</literal> value</title>
-<para>The following command adds that header back:</para>
+<para>The following command adds the <literal>DPAPI</literal> header back:</para>
<programlisting>$encrypted_key_data = [byte]68,80,65,80,73 + $encrypted_key_data</programlisting>
<para>And this one converts it to base64:</para>
<programlisting>$encrypted_key = [Convert]::ToBase64String($encrypted_key_data)</programlisting>
diff --git a/cfa9cc64db910311cd27b0129f0155d58d0a278b46231cd3d409dda99793e74f b/cfa9cc64db910311cd27b0129f0155d58d0a278b46231cd3d409dda99793e74f
index 22b9b08..628889a 100644
--- a/cfa9cc64db910311cd27b0129f0155d58d0a278b46231cd3d409dda99793e74f
+++ b/cfa9cc64db910311cd27b0129f0155d58d0a278b46231cd3d409dda99793e74f
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<section xmlns="http://docbook.org/ns/docbook" xml:id="cfa9cc64db910311cd27b0129f0155d58d0a278b46231cd3d409dda99793e74f">
-<title>Wait, does this mean my Chromium (Electron) data is available to all programs running as the current user?</title>
-<para>Yes.</para>
+<title>What about Electron?</title>
+<para>As of writing, Electron does not support app-bound encryption.
+Unless they use another secret storage, they are still exposed to any program running as the current user.</para>
</section>
diff --git a/dc775bf57f268e573a34292ffc21ae6f67e49871b4354c2895684bd05cfc91a1 b/dc775bf57f268e573a34292ffc21ae6f67e49871b4354c2895684bd05cfc91a1
index e770942..f64acc4 100644
--- a/dc775bf57f268e573a34292ffc21ae6f67e49871b4354c2895684bd05cfc91a1
+++ b/dc775bf57f268e573a34292ffc21ae6f67e49871b4354c2895684bd05cfc91a1
@@ -1,9 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<section xmlns="http://docbook.org/ns/docbook" xml:id="dc775bf57f268e573a34292ffc21ae6f67e49871b4354c2895684bd05cfc91a1">
-<title>TL; DR; Copy; Paste</title>
-<para>First, import <literal>ProtectedData</literal> to PowerShell as described in the immediate next section.
+<title>Decryption</title>
+<para>First, import <literal>System.Security</literal> to PowerShell.
Next, in your Chromium User Data directory (where there is a <literal>Local State</literal> file):</para>
-<programlisting>$json = (Get-Content '.\Local State' -Encoding UTF8 | ConvertFrom-Json); $encrypted_key = $json.os_crypt.encrypted_key; $encrypted_key_data = [Convert]::FromBase64String($encrypted_key)[5..9999]; [System.Security.Cryptography.ProtectedData]::Unprotect($encrypted_key_data, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) | Out-File '.\Local State Unprotected Key'</programlisting>
-<para>The individual steps for the emigration are below.
-After you are done with emigration, go to the next section to perform immigration.</para>
+<para>As <literal>SYSTEM</literal> user, decrypt app-bound key:</para>
+<programlisting>$json = (Get-Content '.\Local State' -Encoding UTF8 | ConvertFrom-Json);
+$app_bound_encrypted_key = $json.os_crypt.app_bound_encrypted_key;
+$app_bound_encrypted_key_data = [Convert]::FromBase64String($app_bound_encrypted_key)[4..9999];
+[System.Security.Cryptography.ProtectedData]::Unprotect($app_bound_encrypted_key_data, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) | Out-File '.\Local State.os_crypt.app_bound_encrypted_key.intermediate'</programlisting>
+<para>As user, decrypt app-bound key:</para>
+<programlisting>$app_bound_intermediate = [byte[]] (Get-Content '.\Local State.os_crypt.app_bound_encrypted_key.intermediate');
+[System.Security.Cryptography.ProtectedData]::Unprotect($app_bound_intermediate, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) | Out-File '.\Local State.os_crypt.app_bound_encrypted_key.unprotected'
+Remove-Item '.\Local State.os_crypt.app_bound_encrypted_key.intermediate'</programlisting>
+<para>As user, decrypt legacy key:</para>
+<programlisting>$json = (Get-Content '.\Local State' -Encoding UTF8 | ConvertFrom-Json);
+$encrypted_key = $json.os_crypt.encrypted_key;
+$encrypted_key_data = [Convert]::FromBase64String($encrypted_key)[5..9999];
+[System.Security.Cryptography.ProtectedData]::Unprotect($encrypted_key_data, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) | Out-File '.\Local State.os_crypt.encrypted_key.unprotected'</programlisting>
</section>