Skip to content

Instantly share code, notes, and snippets.

@mrh1997 mrh1997/wincred.py
Last active Feb 14, 2020

Embed
What would you like to do?
Retrieve Windows Credential via Python
#!python3
"""
Access windows credentials
"""
from typing import Tuple
import ctypes as CT
import ctypes.wintypes as WT
CRED_TYPE_GENERIC = 0x01
LPBYTE = CT.POINTER(WT.BYTE)
LPWSTR = WT.LPWSTR
LPCWSTR = WT.LPWSTR
class CREDENTIAL_ATTRIBUTE(CT.Structure):
_fields_ = [
('Keyword', LPWSTR),
('Flags', WT.DWORD),
('ValueSize', WT.DWORD),
('Value', LPBYTE)]
PCREDENTIAL_ATTRIBUTE = CT.POINTER(CREDENTIAL_ATTRIBUTE)
class CREDENTIAL(CT.Structure):
_fields_ = [
('Flags', WT.DWORD),
('Type', WT.DWORD),
('TargetName', LPWSTR),
('Comment', LPWSTR),
('LastWritten', WT.FILETIME),
('CredentialBlobSize', WT.DWORD),
('CredentialBlob', LPBYTE),
('Persist', WT.DWORD),
('AttributeCount', WT.DWORD),
('Attributes', PCREDENTIAL_ATTRIBUTE),
('TargetAlias', LPWSTR),
('UserName', LPWSTR)]
PCREDENTIAL = CT.POINTER(CREDENTIAL)
advapi32 = CT.WinDLL('Advapi32.dll')
advapi32.CredReadA.restype = WT.BOOL
advapi32.CredReadA.argtypes = [LPCWSTR, WT.DWORD, WT.DWORD, CT.POINTER(PCREDENTIAL)]
def GetGenericCredential(name:str) -> Tuple[str, str]:
"""
Returns a Tuple of Name and Password of a Generic Windows Credential
Uses bytes in Py3 and str in Py2 for url, name and password.
"""
cred_ptr = PCREDENTIAL()
if advapi32.CredReadW(name, CRED_TYPE_GENERIC, 0, CT.byref(cred_ptr)):
username = cred_ptr.contents.UserName
cred_blob = cred_ptr.contents.CredentialBlob
cred_blob_size = cred_ptr.contents.CredentialBlobSize
password_as_list = [int.from_bytes(cred_blob[pos:pos+2], 'little')
for pos in range(0, cred_blob_size, 2)]
password = ''.join(map(chr, password_as_list))
advapi32.CredFree(cred_ptr)
return username, password
else:
raise IOError("Failure reading credential")
def main():
name, pwd = GetGenericCredential('git:https://github.com')
print("GITHUB NAME:", name)
print("GITHUB PASSWORD:", pwd)
if __name__ == '__main__':
main()
@exhuma

This comment has been minimized.

Copy link

exhuma commented Jun 24, 2019

Line 42 looks dodgy:

unicodePassword = credPtr.contents.CredentialBlob[:passwordSize:2]

My instinct tells me that this is probably a byte-value representing a text encoded using something other than UTF-8. Maybe UTF-16? Using the step-size "2" and then converting the bytes blindly using the chr builtin is in that case erroneous at best, or even a security risk at worst.

If my feeling is correct, it would be better to find out which encoding is used in the credential store and then simply use credPtr.contents.CredentialBlob.decode(<encoding-name>)

@mrh1997

This comment has been minimized.

Copy link
Owner Author

mrh1997 commented Jun 24, 2019

Actually this code was written for Python 2.7 ignoring Unicode. For real Unicode support there is more to do:

  • replace CreadReadA by CreadReadW and thus supporting unicode urls
  • replacing all LPTSTR by LPWSTR. This is especially important for UserName as it would be returned as unicode then
  • credPtr.contents.CredentialBlob is of type LP_c_byte. Thus you cannot use the method decode.

I updated the code to support unicode correctly (and switched to Python 3 compatibility)

@exhuma

This comment has been minimized.

Copy link

exhuma commented Jun 25, 2019

I'm curious as I am not familiar with the Windows API: Why is there a step-size of 2 when retrieving the value from the credentials blob?

Do you have a link to the documentation? Or was this reverse engineered?

And thanks a bunch for updating this! I'll give it a go ;)

@exhuma

This comment has been minimized.

Copy link

exhuma commented Jun 25, 2019

I aligned the code closer to PEP8 and made other things a bit more "pythonic" over at https://gist.github.com/exhuma/a310f927d878b3e5646dc67dfa509b42

In case you agree with the changes, you can merge them back if you like. Unfortunatly it's not possible to do PRs on gists yet.

@rolangom

This comment has been minimized.

Copy link

rolangom commented Aug 13, 2019

I'm using this script in a node package. See: https://github.com/rolangom/wincred

@burque505

This comment has been minimized.

Copy link

burque505 commented Feb 14, 2020

I used a scarcely modified version of your code, posted here, to allow automating Box.com with Robin, the RPA language.
Works great, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.