|
1 |
| -from ctypes import * |
2 |
| -from ctypes.wintypes import DWORD |
| 1 | +# Copyright 2015, Nashwan Azhari. |
| 2 | +# Licensed under the GPLv2, see LICENSE file for details. |
3 | 3 |
|
4 |
| -protectdata = windll.crypt32.CryptProtectData |
5 |
| -unprotectdata = windll.crypt32.CryptUnprotectData |
6 |
| -localfree = windll.kernel32.LocalFree |
7 |
| -copy = cdll.msvcrt.memcpy |
| 4 | +""" |
| 5 | +A pure Python implementation of the functionality of the ConvertTo-SecureString |
| 6 | +and ConvertFrom-SecureString PoweShell commandlets. |
8 | 7 |
|
9 |
| -# the basic blob structure we will be using for calling |
10 |
| -# the above System functions |
11 |
| -class blob(Structure): |
12 |
| - _fields_ = [("length", DWORD), ("data", POINTER(c_char))] |
| 8 | +Usage example: |
| 9 | +from securestring import encrypt, decrypt |
13 | 10 |
|
14 |
| -# this function will fetch all the gata from a given blob |
15 |
| -def getblobdata(b): |
16 |
| - length = int(b.length) |
17 |
| - |
18 |
| - fetched = c_buffer(length) |
19 |
| - copy(fetched, b.data, length) |
| 11 | +if __name__ == "__main__": |
| 12 | + str = "My horse is amazing" |
20 | 13 |
|
21 |
| - freeblobdata(b) |
22 |
| - return fetched.raw |
| 14 | + # encryption: |
| 15 | + try: |
| 16 | + enc = encrypt(str) |
| 17 | + print("The encryption of %s is: %s" % (str, enc)) |
| 18 | + except Exception as e: |
| 19 | + print(e) |
23 | 20 |
|
24 |
| -# this function will free the memory of the data field from a given blob |
25 |
| -def freeblobdata(b): |
26 |
| - localfree(b.data) |
| 21 | + # decryption: |
| 22 | + try: |
| 23 | + dec = decrypt(enc) |
| 24 | + print("The decryption of the above is: %s" % dec) |
| 25 | + except Exception as e: |
| 26 | + print(e) |
| 27 | +
|
| 28 | + # checking of operation symmetry: |
| 29 | + print("Encryption and decryption are symmetrical: %r", dec == str) |
| 30 | +
|
| 31 | + # decrypting powershell input: |
| 32 | + psenc = "<your output of ConvertFrom-SecureString>" |
| 33 | + try: |
| 34 | + dec = decrypt(psenc) |
| 35 | + print("Decryption from ConvertFrom-SecureString's input: %s" % dec) |
| 36 | + except Exception as e: |
| 37 | + print(e) |
| 38 | +
|
| 39 | +""" |
| 40 | + |
| 41 | +from codecs import encode |
| 42 | +from codecs import decode |
| 43 | + |
| 44 | +from blob import Blob |
| 45 | + |
| 46 | +from ctypes import byref |
| 47 | +from ctypes import create_string_buffer |
| 48 | +from ctypes import windll |
| 49 | + |
| 50 | +protect_data = windll.crypt32.CryptProtectData |
| 51 | +unprotect_data = windll.crypt32.CryptUnprotectData |
27 | 52 |
|
28 | 53 |
|
29 |
| -# this function will encrypt a given string in accordance with the |
30 |
| -# ConvertFrom-SecureString commandlet and return the hex representation |
31 | 54 | def encrypt(input):
|
32 |
| - # for some odd reason the cmdlet's calls encrypt the data with interwoven |
33 |
| - # nulls, for which we will account as follows: |
34 |
| - nulled = "" |
35 |
| - for char in input: |
36 |
| - nulled = nulled + char + "\x00" |
37 |
| - |
38 |
| - data = c_buffer(nulled, len(nulled)) |
39 |
| - |
40 |
| - inputBlob = blob(len(nulled), data) |
41 |
| - entropyBlob = blob() |
42 |
| - outputBlob = blob() |
43 |
| - flag = 0x01 |
44 |
| - |
45 |
| - res = protectdata(byref(inputBlob), u"", byref(entropyBlob), None, |
46 |
| - None, flag, byref(outputBlob)) |
47 |
| - if res == 0: |
48 |
| - freeblobdata(outputBlob) |
49 |
| - raise Exception("Failed to encrypt " + input) |
50 |
| - else: |
51 |
| - return getblobdata(outputBlob).encode("hex") |
52 |
| - |
53 |
| -# this function will decrypt the output of ConvertFrom-SecureString |
54 |
| -# and return the original string which was encrypted |
| 55 | + """Encrypts the given string following the same syscalls as done by |
| 56 | + ConvertFrom-SecureString. |
| 57 | +
|
| 58 | + Arguments: |
| 59 | + input -- an input string. |
| 60 | +
|
| 61 | + Returns: |
| 62 | + output -- string containing the output of the encryption in hexadecimal. |
| 63 | + """ |
| 64 | + # CryptProtectData takes UTF-16; so we must convert the data here: |
| 65 | + encoded = input.encode("utf-16") |
| 66 | + data = create_string_buffer(encoded, len(encoded)) |
| 67 | + |
| 68 | + # create our various Blobs: |
| 69 | + input_blob = Blob(len(encoded), data) |
| 70 | + output_blob = Blob() |
| 71 | + flag = 0x01 |
| 72 | + |
| 73 | + # call CryptProtectData: |
| 74 | + res = protect_data(byref(input_blob), u"", byref(Blob()), None, |
| 75 | + None, flag, byref(output_blob)) |
| 76 | + input_blob.free_blob() |
| 77 | + |
| 78 | + # check return code: |
| 79 | + if res == 0: |
| 80 | + output_blob.free_blob() |
| 81 | + raise Exception("Failed to encrypt: %s" % input) |
| 82 | + else: |
| 83 | + raw = output_blob.get_data() |
| 84 | + output_blob.free_blob() |
| 85 | + |
| 86 | + # encode the resulting bytes into hexadecimal before returning: |
| 87 | + hex = encode(raw, "hex") |
| 88 | + return decode(hex, "utf-8").upper() |
| 89 | + |
| 90 | + |
55 | 91 | def decrypt(input):
|
56 |
| - rawinput = input.decode("hex") |
57 |
| - data = c_buffer(rawinput, len(rawinput)) |
58 |
| - |
59 |
| - inputBlob = blob(len(rawinput), data) |
60 |
| - entropyBlob = blob() |
61 |
| - outputBlob = blob() |
62 |
| - dwflags = 0x01 |
63 |
| - |
64 |
| - res = unprotectdata(byref(inputBlob), u"", byref(entropyBlob), None, |
65 |
| - None, dwflags, byref(outputBlob)) |
66 |
| - if res == 0: |
67 |
| - freeblobdata(outputBlob) |
68 |
| - raise Exception("Failed to decrypt " + input) |
69 |
| - else: |
70 |
| - clean = "" |
71 |
| - # as mentioned, the commandlets work with data with interwoven nulls, |
72 |
| - # for which we must account for by removing them at the end: |
73 |
| - for char in getblobdata(outputBlob): |
74 |
| - if char != "\x00": |
75 |
| - clean = clean + char |
76 |
| - return clean |
| 92 | + """Decrypts the given hexadecimally-encoded string in conformity |
| 93 | + with CryptUnprotectData. |
| 94 | +
|
| 95 | + Arguments: |
| 96 | + input -- the encrypted input string in hexadecimal format. |
| 97 | +
|
| 98 | + Returns: |
| 99 | + output -- string containing the output of decryption. |
| 100 | + """ |
| 101 | + # de-hex the input: |
| 102 | + rawinput = decode(input, "hex") |
| 103 | + data = create_string_buffer(rawinput, len(rawinput)) |
| 104 | + |
| 105 | + # create out various Blobs: |
| 106 | + input_blob = Blob(len(rawinput), data) |
| 107 | + output_blob = Blob() |
| 108 | + dwflags = 0x01 |
| 109 | + |
| 110 | + # call CryptUnprotectData: |
| 111 | + res = unprotect_data(byref(input_blob), u"", byref(Blob()), None, |
| 112 | + None, dwflags, byref(output_blob)) |
| 113 | + input_blob.free_blob() |
| 114 | + |
| 115 | + # check return code: |
| 116 | + if res == 0: |
| 117 | + output_blob.free_blob() |
| 118 | + raise Exception("Failed to decrypt: %s" + input) |
| 119 | + else: |
| 120 | + raw = output_blob.get_data() |
| 121 | + output_blob.free_blob() |
77 | 122 |
|
| 123 | + # decode the resulting data from UTF-16: |
| 124 | + return decode(raw, "utf-16") |
0 commit comments