DPAPI user-mode access in Python

In the latest version of Tubecaster I’ve come to the point of needing to give users the option of storing passwords on disk. Needless to say the very thought sent shivers down my spine. I was stressing over the problem of how to store passwords for automatic retrieval and use by the software, but at the same time making them secure enough that they can’t easily be retrieved by nasty people. This led me to make this post on Stack Overflow.

Basically the given solution I thought best (at least for Windows platforms at this stage) is to use Windows’ built-in Data Protection API. This led to a follow-up question on Stack Overflow where the user “dF” pointed me to an example which uses Python’s ctypes library. This works really well and I’ve tidied up the example and added a couple of functions to make it more user friendly and play nicely as a library. You can view and download it below. Thanks again to “dF” and “Crusher Joe”.

# DPAPI access library
# This file uses code originally created by Crusher Joe:
# http://article.gmane.org/gmane.comp.python.ctypes/420
#

from ctypes import *
from ctypes.wintypes import DWORD

LocalFree = windll.kernel32.LocalFree
memcpy = cdll.msvcrt.memcpy
CryptProtectData = windll.crypt32.CryptProtectData
CryptUnprotectData = windll.crypt32.CryptUnprotectData
CRYPTPROTECT_UI_FORBIDDEN = 0x01
extraEntropy = "cl;ad13 \0al;323kjd #(adl;k$#ajsd"

class DATA_BLOB(Structure):
    _fields_ = [("cbData", DWORD), ("pbData", POINTER(c_char))]

def getData(blobOut):
    cbData = int(blobOut.cbData)
    pbData = blobOut.pbData
    buffer = c_buffer(cbData)
    memcpy(buffer, pbData, cbData)
    LocalFree(pbData);
    return buffer.raw

def Win32CryptProtectData(plainText, entropy):
    bufferIn = c_buffer(plainText, len(plainText))
    blobIn = DATA_BLOB(len(plainText), bufferIn)
    bufferEntropy = c_buffer(entropy, len(entropy))
    blobEntropy = DATA_BLOB(len(entropy), bufferEntropy)
    blobOut = DATA_BLOB()

    if CryptProtectData(byref(blobIn), u"python_data", byref(blobEntropy),
                       None, None, CRYPTPROTECT_UI_FORBIDDEN, byref(blobOut)):
        return getData(blobOut)
    else:
        return ""

def Win32CryptUnprotectData(cipherText, entropy):
    bufferIn = c_buffer(cipherText, len(cipherText))
    blobIn = DATA_BLOB(len(cipherText), bufferIn)
    bufferEntropy = c_buffer(entropy, len(entropy))
    blobEntropy = DATA_BLOB(len(entropy), bufferEntropy)
    blobOut = DATA_BLOB()
    if CryptUnprotectData(byref(blobIn), None, byref(blobEntropy), None, None,
                              CRYPTPROTECT_UI_FORBIDDEN, byref(blobOut)):
        return getData(blobOut)
    else:
        return ""

def cryptData(text):
    return Win32CryptProtectData(text, extraEntropy)

def decryptData(cipher_text):
    return Win32CryptUnprotectData(cipher_text, extraEntropy)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.