Decrypting Nuget Password
With Data Protection API

I was testing compilation of some .NET projects on a Linux container and needed to add some internal Nuget package sources. I decided to just pull my nuget.config from %AppData%\NuGet\NuGet.Config. Noticed that the dotnet restore was still failing.
I took a closer look at the NuGet.Config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="SomeInternalSource" value="https://wouldnt.you/like/to/know/index.json" />
</packageSources>
<packageSourceCredentials>
<PaymentsArtifactory>
<add key="Username" value="wind" />
<add key="Password" value="AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAVwXHmRrHSUqr3XeHSYQ+eAQAAAACAAAAAAADZgAAwAAAABAAAADc2i71Q5QuVXsuRAOGET9oAAAAAASAAACgAAAAEAAAAJ+eA//MIIa3SBqg22LtLd0gAAAAzfoqMitKya0wANrmkzR0N+BcJWdg+ahgyqD1ZeVQAsUUAAAAVeJZ2/ob46LnaED/xfP1TCAkDc0=" />
</PaymentsArtifactory>
</packageSourceCredentials>
</configuration>
The Password looks to be Base64. Since I’m on Windows, I suspect it’s using the Data Protection API, also see Wikipedia article.
The nuget.exe functionality was rolled into dotnet CLI and the documentation on how to add a new source we see that encryption on Linux is not supported:
Note
Be aware that encrypted passwords are only supported on Windows. Moreover, they can only be decrypted on the same machine and by the same user who originally encrypted them.
I would have to store the password/secret/token in plaintext with:
dotnet nuget add source <my_source> --name SomeInternalSource \
--password "SOME_API_TOKEN" --store-password-in-clear-text
This led me to wonder if I could decrypt my existing passwords since it’s the same machine and user account used to to create the original nuget sources in the NuGet.Config. I could generate a new API token, but curiosity got the better of me.
I created this .NET console project to test the encrypted strings.
Encrypt with:
╰─ .\DPAPICrypto.exe encrypt currentUser "Super super secret!" ─╯
Encrypted Password: AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAbIgpCYlgBkGyuP6LzLedlAAAAAACAAAAAAADZgAAwAAAABAAAABSGbwhI8hNCkKXfI+t04J4AAAAAASAAACgAAAAEAAAAPRa6wYFHvIxBXhFTjAv+AcYAAAAvl83V1iCDdJRWBlD3xXG5VWkbu+0scTCFAAAACxol12xHvqj2kW7NlE2idPetdQ5
Decrypt with:
.\DPAPICrypto.exe decrypt currentUser "AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAbIgpCYlgBkGyuP6LzLedlAAAAAACAAAAAAADZgAAwAAAABAAAABSGbwhI8hNCkKXfI+t04J4AAAAAASAAACgAAAAEAAAAPRa6wYFHvIxBXhFTjAv+AcYAAAAvl83V1iCDdJRWBlD3xXG5VWkbu+0scTCFAAAACxol12xHvqj2kW7NlE2idPetdQ5"
Decrypted Password: Super super secret!
So I try with one my existing secrets and get a CryptographicException thrown: The data is invalid or was encrypted with a different scope or user account.
Looking closer at the SDK/API, the Protect method has an argument for optionalEntropy and the example passes in some byte array. Looks like nuget.exe or the current dotnet nuget code would have injected this parameter if my decrypt method does not work.
return ProtectedData.Protect( data, s_additionalEntropy, DataProtectionScope.CurrentUser );
I search for the dotnet nuget code which only helped in some understand of the dotnet modules. Let’s github dork further into the past for the NuGet.exe code and find it:
private static readonly byte[] _entropyBytes = Encoding.UTF8.GetBytes("NuGet");
Plug that baby in and I was able to decrypt my keys. I noticed that scope didn’t matter if I was decrypting the value and this SO post explains why.
Summary
The risk of this secret being out is minimal since the decryption on the ciphertext still needs to occur on the same machine that encrypted the plaintext. It would however, be a good challenge for a CTF or HtB.
They probably didn’t want to change this value for backwards compatibility.
Don’t store secrets in source code if it’s really meant to be secret. For
.NETuse the UserSecrets for local development or environment variables and some secret store for the CI/CD.TODO: Read more into the inner working of Data Protection API and where the scope affects the encryption/decryption.
References
https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection
https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-add-source
https://stackoverflow.com/questions/2585746/securely-storing-optional-entropy-while-using-dpapi
https://stackoverflow.com/questions/19164926/data-protection-api-scope-localmachine-currentuser
https://en.wikipedia.org/wiki/Capture_the_flag_(cybersecurity)
https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-9.0&tabs=windows




