Am I supposed to get different values each time encrypt string with certificate?


I expected that I would get the same encrypted value when I use certificate encryption of private key for each run? Result is that value is different on each run. I have specific reason why I use PrivateKey instead of PublicKey for encryption. I want to encrypt something with PrivateKey to decrypt later with public Key of the same cert.

$rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
$key = New-Object byte[](32)
$cert = Get-Item Cert:\CurrentUser\My\E098AE75AA72D21A586A4ABC1B586491FF141841
for ($i=1; $i -lt 10; $i++) {
	Write-Output ("Iteration $i")
	Write-Output ($($cert.PrivateKey.Encrypt($key, $true)) | select -First 3)
Iteration 1
Iteration 2
Iteration 3

That’s normal, and nothing to worry about. When you encrypt something with an RSA public key, you’ll always be able to decrypt it with the private key (and vice versa, though “encrypting” with the private key is generally only used for signatures.)

The reason you’re seeing different values is that the encryption algorithm involves generating some random padding.


What am I doing wrong then? I can not decrypt value I encrypted. Also what is second parameter being passed to Encrypt/Decrypt(). I saw it’s used as boolean value but per MSDN documentation it’s enumeration (

$rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
$key = New-Object byte[](32)
$cert = Get-Item Cert:\CurrentUser\My\E098AE75AA72D21A586A4ABC1B586491FF141841
Write-Output $cert.PublicKey.Key.Decrypt($cert.PrivateKey.Encrypt($key, $true), $true)
ERROR: Exception calling "Decrypt" with "2" argument(s): "Error occurred while decoding OAEP padding."
tes.ps1 (5, 1): ERROR: At Line: 5 char: 1
ERROR: + Write-Output $cert.PublicKey.Key.Decrypt($cert.PrivateKey.Encrypt($key, $true),  ...
ERROR: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ERROR:     + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
ERROR:     + FullyQualifiedErrorId : CryptographicException

Looks like maybe encrypting with the private key isn’t working. (Mathematically, there’s no reason it shouldn’t; that’s how digital signatures work anyway. But maybe there’s something in the API which doesn’t work that way.)

Works fine if you reverse it and encrypt with the public key, decrypt with private.

What are you attempting to do here, exactly? You should assume that everyone in the world has your public key, so there’s not really any privacy gained in encrypting actual data that way.

I need to deploy certificate to all machines I manage with GPO. GPO can not push certificate with private key, only public key is left after deployment. I need only machines which has this certificate to be able to decrypt value I will give to them. I don’t see any other way to do that except to reverse encryption path. I have an option to pass plain password to all machines via script content, registry entry or I have option at least to use some encryption on top of it (even if reverse order), so I choose this scheme since I don’t have much other alternvatives.

Yeah, you may as well just use plain text, at that point. Public keys are not protected in any way whatsoever, and you can assume that anyone has them (regardless of which machines you intended to have the certificate).

PowerShell Remoting may give you a viable alternative. You can set up a remoting endpoint and give permissions to AD machine accounts (or even better, a group containing AD computer accounts.) Then anything running as System on those computers will be able to connect to your endpoint (which should be secured with HTTPS, considering what you’re trying to do). That endpoint can provide a command to fetch whatever value it is you’re trying to send out. No manual encryption needed, at that point, and you have access control to the endpoint.

Boe Prox did a great series of Hey, Scripting Guy blog posts on remoting endpoints. Here’s a link to part 5 in the series (which contains links to the other 4):

Well the problem is that I need to have a second hop to be available. I need to run script on remote machine using WinRM but that script shall be able to access network resources. Certificate exists only on machines it’s configured at so while it’s not as protected as private key, I still think it’s better approach then just to hardcode plain passwords in script.
Basically I want to use WinRM, pass script to remote machine and that script while executing on remote machine needs to pull files from UNC share.

What you are attempting here is not how RSACryptoServiceProvider works.

The Encrypt method always uses the public key. The Decrypt method always uses the private key. The normal use of these two methods is to exchange a session key. The “client” will generate a random session key (for whatever symmetric algorithm has been agreed upon) and Encrypt that session key. The encrypted data is sent to the “server”, which will Decrypt it to get the session key.

The direction you are trying to go is normally used for digital signatures. The holder of the private key calls SignData or SignHash to create a signature. The recipient calls VerifyData or VerifyHash, which uses the public key, to verify the signature.

What is the point of having method called “Encrypt” on “PrivateKey” object if it’s not actually using PrivateKey to Encrypt? Why does it exists?
I understand ramification and what I’m doing is not what I’m supposed to do but it’s better then sending plain password for sure. I have certificate which nobody has. I can paste stuff encrypted with PrivateKey in this forum and nobody will be able to decrypt it because they don’t have public key for it. I outlined above why I’m doing it. I need large scale WinRM automation to use second-hop scenario to be enabled. Unless I pass clear text password in all my script I don’t see how I can effectively implement it since GPO does not allow to distribute certificates with private key attached unfortunately.

The PrivateKey and PublicKey.Key objects are both of the same class: RsaCryptoServiceProvider. They’re just created with different key material.

As for why it doesn’t work to encrypt with private / decrypt with public, I have no idea. As I said earlier, mathematically, the RSA algorithm works both ways (and you “encrypt” a hash with the private key when generating a digital signature.) However, I’m not surprised that it doesn’t work and no one noticed, because encrypting actual data with a private key does not provide any security benefit.

As far as I understand:

  1. Encrypting value with Private or Public key provides the same cryptographic strength to encryption.
  2. The only difference is how difficult is it to get Public vs Private key. For self-signed certificate which only I have there is no difference or at least I don’t see one. The only one may be is that you can secure access to PrivateKey via ACL.
    If .NET implemenation would provide proper handling of encryption with PrivateKey I would have had working solution to my project. This is roadblock with no working solution other then just provide password in cleartext to enable second hop authentication.

You could store the password as an encrypted secure string. This would enable you to store the password as ciphertext rather than plaintext. If you stored it in a text file or registry key you could further secure the file by allowing only the account that’s running the script to read the file/key. Look at the help for ConvertFrom-SecureString for guidance. There are also several step-by-step guides online.

It’s not going to work in distributed environment. I need to run script on remote computer using WinRM which will access network resources as specific account. SecureString storage will not work in this case.

“The only difference is how difficult is it to get Public vs Private key.”

Well, yes, but that’s a rather large difference. :slight_smile: The public key is literally known to anyone who cares to read it. Even without ACLs, there are a lot of measures (several layers of encryption) that go into protecting a private key once a user has installed or created a certificate.

Personally, based on what I’ve heard about your code, you should just have your script accept a PSCredential as an argument, and pass that over the WinRM session when you call the script. That way you only have to store the password in one place (the computer that’s kicking off the script). You might still need to use an HTTPS connection for WinRM; I’m not 100% certain whether the serialized PSCredential object will have an encrypted password when it goes across the network, but that’s easy enough to test with WireShark or something.

Wow, thanks for suggestion. This will work really well if it’s in fact possible, I did not think about passing PSCredential over. For my scenario would work really well (where I kick off remote script from central location). For other implementation (like having scheduled task being run on remote computers) I’m still lost how Powershell architecture expects to provide stored credentials effectively and securely in large scale deployments.

For scheduled tasks, my preferred approach is to set up the scheduled task to run AS the account that has permissions. That way, you don’t need to specify any credentials in the script itself, and Windows takes care of storing passwords in a way that is reasonably secure. (With the usual caveat that someone who gains Administrator access to a computer, or physical access to a hard drive, can still take pretty much whatever they want.)

Just ran a quick test of this with WireShark. I can’t tell you how the PSCredential object itself is handled, because the whole session was encrypted, even without HTTPS. I ran two tests, one with Kerberos authentication and one with Negotiate. In both cases, the HTTP traffic was still encrypted, with MIME content types of either “multipart/encrypted;protocol=“application/HTTP-SPNEGO-session-encrypted”” or “multipart/encrypted;protocol=“application/HTTP-Kerberos-session-encrypted””. I didn’t go as far as jumping through hoops to enable Basic authentication over HTTP, because why would you ever do that anyway? :slight_smile:

Learned something new today. I remember reading that Kerberos would give you some encryption even without HTTPS, but I didn’t realize the same was true for NTLM.

You mean SOAP payload is actually encrypted (where PSCredential will be residing)? Wondering what WinRM is using for encryption/decryption of that payload on endpoint.

Looks like it’s using this function from the SSPI library: . At least that’s how Chef’s winrm Ruby library appears to work: