As a IT Tech, I do alot of automation scripting, both in Powershell and Python against various systems and Ive never liked how I had to handle credentials in scripts.
I dont want to have any credentials or API-keys in plain text on a server that others have access to, so I saved it as environment variables like most of us do.
That works fine, but when you have hundreds of variables for email accounts, API-keys and SFTP accounts it can get messy.
When I looked for a better solution I found this gem for Powershell!
All commands below are run from PowerShell.
Prompt for Username and Password and save to file
PS C:\> Get-Credential | Export-Clixml -Path "cred.xml"
Import the credentials from a file to a PSCredential object.
PS C:\> $cred = Import-Clixml -Path "cred.xml"
PS C:\> $cred
UserName Password
-------- --------
samkling System.Security.SecureString
PS C:\> ConvertFrom-SecureString $cred.password -AsPlainText
password
ConvertFrom-SecureString -AsPlainText requires PowerShell 7.0.
Export-Clixml only exports encrypted credentials on Windows.
The Export-Clixml cmdlet encrypts credential objects by using the Windows Data Protection API . The encryption ensures that only your user account on only that computer can decrypt the contents of the credential object. The exported CLIXML file can't be used on a different computer or by a different user.
I now store the credentials neatly, and secure in a credentials.xml file in the same directory as the actual script. Anyone can access the credential file and the script, but they wont be able to decrypt
.
├─ company1-sftp-script
│ ├─ download-files-newer-than-1-day.ps1
│ └─ credentials.xml
└─ company2-sftp-script
├─ download-files-newer-than-1-day.ps1
└─ credentials.xml
How can we use the same method in Python?
Sadly there is no Export-Clixml/Import-Clixml equalent for Python so we will have to build it ourselves.
First we need to have access Windows Data Protection API. There are several ways, I use pywin32.
PS C:\> pip install pywin32
And then we need to create two files in python.
# export_clixml.py
import win32crypt
import binascii
import sys
def export_clixml(username, password):
# encrypt the password with DPAPI.
crypted_password = win32crypt.CryptProtectData(
password.encode("utf-16-le"), None, None, None, None, 0
)
# Do some magic to return the password in the exact same format as if you would use Powershell.
password_secure_string = binascii.hexlify(crypted_password).decode()
# Use the same xml format as for powershells Export-Clixml, just replace values for username and password.
xml = f"""<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>System.Management.Automation.PSCredential</T>
<T>System.Object</T>
</TN>
<ToString>System.Management.Automation.PSCredential</ToString>
<Props>
<S N="UserName">{username}</S>
<SS N="Password">{password_secure_string}</SS>
</Props>
</Obj>
</Objs>"""
return xml
if __name__ == "__main__":
if len(sys.argv) == 3:
# Dont do this, It's just so that we can pipe the output to file to mimic the powershells version.
print(export_clixml(sys.argv[1], sys.argv[2]))
# import_clixml.py
import win32crypt
import binascii
import sys
def import_clixml(filename):
with open(filename, "r", encoding="utf-8") as f:
xml = f.read()
# Extract username and password from the XML since thats all we care about.
username = xml.split('<S N="UserName">')[1].split("</S>")[0]
password_secure_string = xml.split('<SS N="Password">')[1].split("</SS>")[0]
# CryptUnprotectDate returns two values, description and the password,
# we dont care about the description, so we use _ as variable name.
_, decrypted_password_string = win32crypt.CryptUnprotectData(
binascii.unhexlify(password_secure_string), None, None, None, 0
)
return f"{username}, {decrypted_password_string.decode()}"
if __name__ == "__main__":
# We use sys args just to mimic the powershell version.
if len(sys.argv) == 2:
print(import_clixml(sys.argv[1]))
Lets try it out and see if we can use Powershells Export-Clixml and Pythons Import-Clixml.
PS C:\> Get-Credential | Export-Clixml -Path powershell_cred.xml
PowerShell credential request
Enter your credentials.
User: samkling
Password: password
PS C:\> py .\Import_Clixml.py .\powershell_cred.xml
samkling, password
Sweet, so it returned the correct username and password decrypted.
What about the other way around? From Python to Powershell?
PS C:\> $cred = Import-Clixml -Path .\python_cred.xml
PS C:\> $cred
UserName Password
-------- --------
samkling System.Security.SecureString
PS C:\> ConvertFrom-SecureString $cred.password -AsPlainText
password
How cool is that!
Top comments (0)