CryptographicException: Key not valid for use in specified state.

Context:

You may see the following exception when accessing several claims based Web applications behind a load balancer either in .NET 3.5, .NET 4.0 or .NET 4.5:

 

 

[CryptographicException: Key not valid for use in specified state.

]

   System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope) +397

   System.IdentityModel.ProtectedDataCookieTransform.Decode(Byte[] encoded) +90

 

[InvalidOperationException: ID1073: A CryptographicException occurred when attempting to decrypt the cookie using the ProtectedData API (see inner exception for details). If you are using IIS 7.5, this could be due to the loadUserProfile setting on the Application Pool being set to false. ]

   System.IdentityModel.ProtectedDataCookieTransform.Decode(Byte[] encoded) +1158374

   System.IdentityModel.Tokens.SessionSecurityTokenHandler.ApplyTransforms(Byte[] cookie, Boolean outbound) +173

   System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver) +756

   System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(Byte[] token, SecurityTokenResolver tokenResolver) +100

   System.IdentityModel.Services.SessionAuthenticationModule.ReadSessionTokenFromCookie(Byte[] sessionCookie) +668

   System.IdentityModel.Services.SessionAuthenticationModule.TryReadSessionTokenFromCookie(SessionSecurityToken& sessionToken) +164

   System.IdentityModel.Services.SessionAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs eventArgs) +173

   System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +92

   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +165

 

 

 

NB:

A great explanation of the issue was provided in the following blog: A CryptographicException occurred when attempting to decrypt the cookie using the ProtectedData API

However, I feel like some parts need some additional info.

 

 

 

Resolution:

This exception usually occurs when there is a load balancer that routes incoming user requests to one of the servers in a cluster environment.

By default, DPAPI is used to secure the user session cookie.

Therefore, the exception will occur if the request gets routed to a different server than the one that serviced the first request for that session.

There are three ways to resolved this issue:

 

 

1- Use sticky sessions at the Load Balancer

 

 

2- Use X509 Certificate to secure sessions cookies

 

Below is the way to do that:

 

For framework 4.5:

==============

 

C#:

 

 void OnFederationConfigurationCreated(object sender, FederationConfigurationCreatedEventArgs e)

{

var sessionTransforms = new List<CookieTransform>(new CookieTransform[]

{

new DeflateCookieTransform(),

new RsaEncryptionCookieTransform(e.FederationConfiguration.ServiceCertificate),

new RsaSignatureCookieTransform(e.FederationConfiguration.ServiceCertificate)

});

var sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());


e.FederationConfiguration

.IdentityConfiguration

.SecurityTokenHandlers

.AddOrReplace(sessionHandler);

}

 

Config:

 

 <system.identityModel.services>

<federationConfiguration>

--------


<serviceCertificate>

<certificateReference x509FindType="FindByThumbprint" findValue="[yourCorrespondingThumbprint]"/>

</serviceCertificate>


</federationConfiguration>

</system.identityModel.services>

 

 

 

For framework 4.0 and 3.5:

==========================

 

C#:

 

 void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)

{

var sessionTransforms = new List<CookieTransform>(new CookieTransform[] 

{

new DeflateCookieTransform(), 

new RsaEncryptionCookieTransform(

e.ServiceConfiguration.ServiceCertificate),

new RsaSignatureCookieTransform(

e.ServiceConfiguration.ServiceCertificate) 

});

var readOnlyTransforms = sessionTransforms.AsReadOnly();

var sessionHandler = new SessionSecurityTokenHandler(readOnlyTransforms);


e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);

}

 

 

Config:

 

 <microsoft.identityModel>

<service>

...

<serviceCertificate>

<certificateReference x509FindType="FindByThumbprint" findValue="[yourCorrespondingThumbprint]"/>

</serviceCertificate>

</service>

</microsoft.identityModel>

 

NB: certificate has to be the same across all the servers. That is, we should have the same yourCorrespondingThumbprint across all the servers behind LB

 

 

3- Use DPAPI with matching machine keys

In this case, you can generate the machine key in IIS as follows:

clip_image001[4]

clip_image002[4]

Then, Add the following entry in </system.web> of C:\Windows\Microsoft.NET\Framework64\<Version>\Config\Machine.Config:

<machineKey decryption="Auto" decryptionKey="BA43FB8C9792E9D35F98942B429611584F682394E65DC452" validationKey=" 55941848BB4B57BB9E9D882DD3DF9D2EA4CC174631132832D3780F42A09B9BBA8EC82E92693AC2B13F2E99A5D48C637BA47301366CF622DCA9B0AD3E18E26DC1