1#! /usr/bin/env python
2# encoding: utf-8
3# Matt Clarkson, 2012
4
5'''
6DPAPI access library (http://msdn.microsoft.com/en-us/library/ms995355.aspx)
7This file uses code originally created by Crusher Joe:
8http://article.gmane.org/gmane.comp.python.ctypes/420
9And modified by Wayne Koorts:
10http://stackoverflow.com/questions/463832/using-dpapi-with-python
11'''
12
13from ctypes import windll, byref, cdll, Structure, POINTER, c_char, c_buffer
14from ctypes.wintypes import DWORD
15from waflib.Configure import conf
16
17LocalFree = windll.kernel32.LocalFree
18memcpy = cdll.msvcrt.memcpy
19CryptProtectData = windll.crypt32.CryptProtectData
20CryptUnprotectData = windll.crypt32.CryptUnprotectData
21CRYPTPROTECT_UI_FORBIDDEN = 0x01
22try:
23	extra_entropy = 'cl;ad13 \0al;323kjd #(adl;k$#ajsd'.encode('ascii')
24except AttributeError:
25	extra_entropy = 'cl;ad13 \0al;323kjd #(adl;k$#ajsd'
26
27class DATA_BLOB(Structure):
28	_fields_ = [
29		('cbData', DWORD),
30		('pbData', POINTER(c_char))
31	]
32
33def get_data(blob_out):
34	cbData = int(blob_out.cbData)
35	pbData = blob_out.pbData
36	buffer = c_buffer(cbData)
37	memcpy(buffer, pbData, cbData)
38	LocalFree(pbData)
39	return buffer.raw
40
41@conf
42def dpapi_encrypt_data(self, input_bytes, entropy = extra_entropy):
43	'''
44	Encrypts data and returns byte string
45
46	:param input_bytes: The data to be encrypted
47	:type input_bytes: String or Bytes
48	:param entropy: Extra entropy to add to the encryption process (optional)
49	:type entropy: String or Bytes
50	'''
51	if not isinstance(input_bytes, bytes) or not isinstance(entropy, bytes):
52		self.fatal('The inputs to dpapi must be bytes')
53	buffer_in      = c_buffer(input_bytes, len(input_bytes))
54	buffer_entropy = c_buffer(entropy, len(entropy))
55	blob_in        = DATA_BLOB(len(input_bytes), buffer_in)
56	blob_entropy   = DATA_BLOB(len(entropy), buffer_entropy)
57	blob_out       = DATA_BLOB()
58
59	if CryptProtectData(byref(blob_in), 'python_data', byref(blob_entropy),
60		None, None, CRYPTPROTECT_UI_FORBIDDEN, byref(blob_out)):
61		return get_data(blob_out)
62	else:
63		self.fatal('Failed to decrypt data')
64
65@conf
66def dpapi_decrypt_data(self, encrypted_bytes, entropy = extra_entropy):
67	'''
68	Decrypts data and returns byte string
69
70	:param encrypted_bytes: The encrypted data
71	:type encrypted_bytes: Bytes
72	:param entropy: Extra entropy to add to the encryption process (optional)
73	:type entropy: String or Bytes
74	'''
75	if not isinstance(encrypted_bytes, bytes) or not isinstance(entropy, bytes):
76		self.fatal('The inputs to dpapi must be bytes')
77	buffer_in      = c_buffer(encrypted_bytes, len(encrypted_bytes))
78	buffer_entropy = c_buffer(entropy, len(entropy))
79	blob_in        = DATA_BLOB(len(encrypted_bytes), buffer_in)
80	blob_entropy   = DATA_BLOB(len(entropy), buffer_entropy)
81	blob_out       = DATA_BLOB()
82	if CryptUnprotectData(byref(blob_in), None, byref(blob_entropy), None,
83		None, CRYPTPROTECT_UI_FORBIDDEN, byref(blob_out)):
84		return get_data(blob_out)
85	else:
86		self.fatal('Failed to decrypt data')
87
88