保存凭据以供 powershell 重用和错误 ConvertTo-SecureString : Key not valid for use in specified state

Posted

技术标签:

【中文标题】保存凭据以供 powershell 重用和错误 ConvertTo-SecureString : Key not valid for use in specified state【英文标题】:Saving credentials for reuse by powershell and error ConvertTo-SecureString : Key not valid for use in specified state 【发布时间】:2011-08-18 15:24:56 【问题描述】:

我正在执行本文中描述的操作,将凭据保存在安全文件中,以便我们的自动化进程可以使用它通过 Invoke-command 运行远程 PS 脚本: http://blogs.technet.com/b/robcost/archive/2008/05/01/powershell-tip-storing-and-using-password-credentials.aspx

当我在我的帐户下运行它时效果很好 - 从加密文件中读取密码,传递给 Invoke-command,一切都很好。

今天,当我的脚本准备好进入黄金时段时,我尝试在将由自动化进程使用的 Windows 帐户下运行它,并在我的脚本尝试从文件中读取安全密码时出现以下错误:

ConvertTo-SecureString : Key not valid for use in specified state.
At \\remoted\script.ps1:210 char:87
+ $password = get-content $PathToFolderWithCredentials\pass.txt | convertto-sec
urestring <<<<
    + CategoryInfo          : InvalidArgument: (:) [ConvertTo-SecureString], C
   ryptographicException
    + FullyQualifiedErrorId : ImportSecureString_InvalidArgument_Cryptographic
   Error,Microsoft.PowerShell.Commands.ConvertToSecureStringCommand

让我的同事在他的帐户下运行,他得到了同样的错误。

这是我用来保存凭据的代码:

$PathToFolderWithCredentials = "\\path\removed"

write-host "Enter login as domain\login:"
read-host | out-file $PathToFolderWithCredentials\login.txt

write-host "Enter password:"
read-host -assecurestring | convertfrom-securestring | out-file $PathToFolderWithCredentials\pass.txt

write-host "*** Credentials have been saved to $pathtofolder ***"

这是脚本中的代码,由自动化进程运行以读取它们以在 Invoke-command 中使用:

$login= get-content $PathToFolderWithCredentials\login.txt
$password = get-content $PathToFolderWithCredentials\pass.txt | convertto-securestring
$credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $login,$password

错误发生在 $password = get-content $PathToFolderWithCredentials\pass.txt |转换为安全字符串

有什么想法吗?

【问题讨论】:

我读到:“ConvertFrom-SecureString cmdlet 使用 Windows 标准数据保护 API 加密此数据。这确保只有您的用户帐户才能正确解密其内容”。这就是为什么它不起作用...任何想法是保存密码的最佳方法,然后可以由另一个 Windows 帐户解密? 发现这篇博文也很有帮助powertoe.wordpress.com/2011/06/05/… 我还建议您阅读“Powershell Cookbook” - 适合初学者和高级用户的 goob 书。它涵盖了这一点以及许多其他内容。 【参考方案1】:

您必须在同一台计算机上创建密码字符串,并使用您将用于运行它的相同登录名。

【讨论】:

"...并且使用相同的登录名。"这对我来说很重要。 "...并且使用相同的登录名。" 【参考方案2】:

ConvertFrom-SecureString 采用Key(和SecureKey)参数。您可以指定密钥来保存加密的标准字符串,然后在ConvertTo-SecureString 中再次使用该密钥来取回安全字符串,而与用户帐户无关。

http://technet.microsoft.com/en-us/library/dd315356.aspx

在一个项目中,我实现了非对称加密,人们使用公钥加密密码,自动化过程使用私钥解密密码:Handling passwords in production config for automated deployment

【讨论】:

谢谢,它有效!你又救了我! :) 只是一个问题(可能是愚蠢的),但如果我在脚本中输入一个密钥,这与在那里输入密码不一样吗? 我没有过多地使用密钥方法,但一般的想法是你将它放在运行脚本的机器上的特定位置(密钥库),脚本从那里读取并解密。在非对称版本中,公钥是已知的,因此人们可以加密密码,但只有拥有私钥的人才能解密。私钥在运行脚本的机器上。脚本只知道在哪里寻找它。希望这是有道理的。【参考方案3】:

下面将允许将凭据保存为文件,然后由另一个用户远程运行的另一个脚本使用这些凭据。

代码取自 David Lee 的一篇很棒的文章,我自己只是做了一些小的调整https://blog.kloud.com.au/2016/04/21/using-saved-credentials-securely-in-powershell-scripts/

第一步是使用 AES 将安全密码保存到文件中。以下将作为独立脚本运行:

            # Prompt you to enter the username and password
            $credObject = Get-Credential

            # The credObject now holds the password in a ‘securestring’ format
            $passwordSecureString = $credObject.password

            # Define a location to store the AESKey
            $AESKeyFilePath = “aeskey.txt”
            # Define a location to store the file that hosts the encrypted password
            $credentialFilePath = “credpassword.txt”

            # Generate a random AES Encryption Key.
            $AESKey = New-Object Byte[] 32
            [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)

            # Store the AESKey into a file. This file should be protected! (e.g. ACL on the file to allow only select people to read)

            Set-Content $AESKeyFilePath $AESKey # Any existing AES Key file will be overwritten

            $password = $passwordSecureString | ConvertFrom-SecureString -Key $AESKey

            Add-Content $credentialFilePath $password

然后在您需要使用凭据的脚本中使用以下内容:

            #set up path and user variables
            $AESKeyFilePath = “aeskey.txt” # location of the AESKey                
            $SecurePwdFilePath = “credpassword.txt” # location of the file that hosts the encrypted password                
            $userUPN = "domain\userName" # User account login 

            #use key and password to create local secure password
            $AESKey = Get-Content -Path $AESKeyFilePath 
            $pwdTxt = Get-Content -Path $SecurePwdFilePath
            $securePass = $pwdTxt | ConvertTo-SecureString -Key $AESKey

            #crete a new psCredential object with required username and password
            $adminCreds = New-Object System.Management.Automation.PSCredential($userUPN, $securePass)

            #use the $adminCreds for some task
            some-Task-that-needs-credentials -Credential $adminCreds

请注意,如果用户可以访问密码文件和密钥文件,他们可以为用户解密密码。

【讨论】:

对于那些使用New-Object System.Net.NetworkCredential($userUPN, $securePass) 的人,只需将其替换为New-Object System.Management.Automation.PSCredential($userUPN, $securePass) ,假设您创建文件并遵循脚本逻辑的步骤,它应该可以正常工作。似乎对我来说工作得很好,正是我正在寻找的东西,而不是需要在不使用此方法运行的每台机器或配置文件上使用 Get-Credential 定义凭据。谢谢克里斯!!【参考方案4】:

另一种方法是使用范围“LocalMachine”而不是“CurrentUser”来保护数据,后者是 ConvertFrom-SecureString 使用的范围。

public static string Protect(SecureString input, DataProtectionScope dataProtectionScope = DataProtectionScope.CurrentUser, byte[] optionalEntropy = null)

    byte[] data = SecureStringToByteArray(input);
    byte[] data2 = ProtectedData.Protect(data, optionalEntropy, dataProtectionScope);
    for (int i = 0; i < data.Length; i++)
    
        data[i] = 0;
    

    return ByteArrayToString(data2);

private static byte[] SecureStringToByteArray(SecureString s)

    var array = new byte[s.Length * 2];
    if (s.Length > 0)
    
        IntPtr intPtr = Marshal.SecureStringToGlobalAllocUnicode(s);
        try
        
            Marshal.Copy(intPtr, array, 0, array.Length);
        
        finally
        
            Marshal.FreeHGlobal(intPtr);
        
    

    return array;

private static string ByteArrayToString(byte[] data)

    var stringBuilder = new StringBuilder();
    for (int i = 0; i < data.Length; i++)
    
        stringBuilder.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
    

    return stringBuilder.ToString();

加密的字符串可以被使用范围'CurrentUser'的ConvertTo-SecureString使用。

【讨论】:

由于原始问题使用PowerShell,您能否将此答案从C# 也转换为PowerShell【参考方案5】:

假设您有一个已知的 N 个用户列表,这些用户将使用这些凭据(例如,一个开发人员 userMe 和一个系统/服务用户 userSys),您可以(让这些用户)制作 N 个 @987654323 副本@ 文件:每个用户一个。

所以userX 的密码将导致例如2 个*.pass.txt 文件:

userX.userMe.pass.txt userX.userSys.pass.txt

当 userMe 想要他/她阅读 userX.userMe.pass.txt 等的信用时。

【讨论】:

以上是关于保存凭据以供 powershell 重用和错误 ConvertTo-SecureString : Key not valid for use in specified state的主要内容,如果未能解决你的问题,请参考以下文章

iOS,Swift:如何保存包含自定义对象的数组以便我可以加载以供重用?

在Powershell脚本中安全的使用密码信息

如何“取消处置”动画控制器以供重用?

在 C# 中使用不同的凭据调用远程 powershell 脚本

Excel RefreshAll 使用 powershell 脚本和凭据

powershell PowerShell中的用户凭据对象,带有TridionModules和核心服务