Use Windows Data Protection API with Python for handling credentials.
Samuel Kling
Posted on January 12, 2022
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!
Posted on January 12, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 5, 2024
October 3, 2024