# ===========
# pysap - Python library for crafting SAP's network protocols packets
#
# Copyright (C) 2012-2017 by Martin Gallo, Core Security
#
# The library was designed and developed by Martin Gallo from
# Core Security's CoreLabs team.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# ==============
# Standard imports
import os
import math
# Optional imports
try:
from cryptography.exceptions import InvalidKey
from cryptography.hazmat.primitives import constant_time, padding
from cryptography.hazmat.primitives.hashes import Hash
from cryptography.hazmat.primitives.ciphers import Cipher, modes
except ImportError:
Cipher = Hash = None
[docs]def dpapi_decrypt_blob(blob, entropy=None):
"""Decrypts a blob of data using DPAPI.
:param blob: encrypted blob to decrypt
:type blob: string
:param entropy: additional entropy to use to decrypt
:type entropy: string
:return: decrypted blob
:rtype: string
:raise Exception: if the platform is not Windows or the decryption failed
"""
if os.name != "nt":
raise Exception("DP API functions are only available in Windows")
blob = bytes(blob)
entropy = bytes(entropy or "")
from ctypes import windll, byref, cdll, Structure, POINTER, c_char, c_buffer
from ctypes.wintypes import DWORD
LocalFree = windll.kernel32.LocalFree
memcpy = cdll.msvcrt.memcpy
CryptUnprotectData = windll.crypt32.CryptUnprotectData
CRYPTPROTECT_UI_FORBIDDEN = 0x01
class DATA_BLOB(Structure):
_fields_ = [('cbData', DWORD),
('pbData', POINTER(c_char))
]
def parse_data(data):
cbData = int(data.cbData)
pbData = data.pbData
buffer = c_buffer(cbData)
memcpy(buffer, pbData, cbData)
LocalFree(pbData)
return buffer.raw
buffer_in = c_buffer(blob, len(blob))
buffer_entropy = c_buffer(entropy, len(entropy))
blob_in = DATA_BLOB(len(blob), buffer_in)
blob_entropy = DATA_BLOB(len(entropy), buffer_entropy)
blob_out = DATA_BLOB()
if CryptUnprotectData(byref(blob_in), None, byref(blob_entropy), None, None,
CRYPTPROTECT_UI_FORBIDDEN, byref(blob_out)):
return parse_data(blob_out)
raise Exception("Unable to decrypt blob")
[docs]class PKCS12_PBKDF1(object):
def __init__(self, algorithm, length, salt, iterations, id, backend):
self._algorithm = algorithm
self._length = length
if not isinstance(salt, bytes):
raise TypeError("salt must be bytes.")
self._salt = salt
self._iterations = iterations
self._id = id
self._backend = backend
[docs] def derive(self, password):
if not isinstance(password, bytes):
raise TypeError("key_material must be bytes.")
# Convert the password to UNICODE
password = password.decode('utf-8').encode("utf-16be") + b'\x00\x00'
u = self._algorithm.digest_size
v = self._algorithm.block_size
# Step 1 - Concatenate v/8 copies of ID
d = chr(self._id) * v
def concatenate_string(inp):
s = b''
if inp != b'':
s_len = v * int(math.ceil(float(len(inp)) / v))
while len(s) < s_len:
s += inp
s = s[0:s_len]
return s
# Step 2 - Concatenate copies of the salt
s = concatenate_string(self._salt)
# Step 3 - Concatenate copies of the password
p = concatenate_string(password)
# Step 4 - Set I=S||P to be the concatenation of S and P
i = s + p
# Step 5 - Set c=ceiling(n/u)
c = int(math.ceil(float(self._length) / u))
# Step 6
def digest(inp):
h = Hash(self._algorithm(), backend=self._backend)
h.update(inp)
return h.finalize()
def to_int(value):
if value == b'':
return 0
return long(value.encode("hex"), 16)
def to_bytes(value):
value = "%x" % value
if len(value) & 1:
value = "0" + value
return value.decode("hex")
a = b'\x00' * (c * u)
for n in range(1, c + 1):
a2 = digest(d + i)
for _ in range(2, self._iterations + 1):
a2 = digest(a2)
if n < c:
# Step 6.B
b = b''
while len(b) < v:
b += a2
b = to_int(b[:v]) + 1
# Step 6.C
for n2 in range(0, len(i) // v):
start = n2 * v
end = (n2 + 1) * v
i_n2 = i[start:end]
i_n2 = to_bytes(to_int(i_n2) + b)
i_n2_l = len(i_n2)
if i_n2_l > v:
i_n2 = i_n2[i_n2_l - v:]
i = i[0:start] + i_n2 + i[end:]
# Step 7
start = (n - 1) * u
piece = min(self._length, u)
a = a[:start] + a2[:piece] + a[start + piece:]
return a[:self._length]
[docs] def verify(self, key_material, expected_key):
derived_key = self.derive(key_material)
if not constant_time.bytes_eq(derived_key, expected_key):
raise InvalidKey("Keys do not match.")
[docs]class PBKDF1(object):
def __init__(self, algorithm, length, salt, iterations, backend):
self._algorithm = algorithm
self._length = length
if not isinstance(salt, bytes):
raise TypeError("salt must be bytes.")
self._salt = salt
self._iterations = iterations
self._backend = backend
[docs] def derive(self, key_material):
if not isinstance(key_material, bytes):
raise TypeError("key_material must be bytes.")
h = Hash(self._algorithm(), backend=self._backend)
h.update(key_material)
h.update(self._salt)
derived_key = h.finalize()
for i in xrange(self._iterations-1):
h = Hash(self._algorithm(), backend=self._backend)
h.update(derived_key)
derived_key = h.finalize()
return derived_key
[docs] def verify(self, key_material, expected_key):
derived_key = self.derive(key_material)
if not constant_time.bytes_eq(derived_key, expected_key):
raise InvalidKey("Keys do not match.")
[docs]class PKCS12_PBES1(object):
def __init__(self, salt, iterations, iv, password, hash_algorithm, enc_algorithm, enc_mode, backend):
self._hash_algorithm = hash_algorithm
self._enc_algorithm = enc_algorithm
self._enc_mode = enc_mode
self._backend = backend
self._derive_key, self._iv = self.derive_key(salt, iterations, password)
[docs] def derive_key(self, salt, iterations, password):
pkcs12_pbkdf1 = PKCS12_PBKDF1(self._hash_algorithm, 24, salt, iterations, 1, self._backend)
key = pkcs12_pbkdf1.derive(password)
pkcs12_pbkdf1 = PKCS12_PBKDF1(self._hash_algorithm, 8, salt, iterations, 2, self._backend)
iv = pkcs12_pbkdf1.derive(password)
return key, iv
[docs] def encrypt(self, plain_text):
padder = padding.PKCS7(self._hash_algorithm.block_size).padder()
plain_text = padder.update(plain_text) + padder.finalize()
encryptor = Cipher(self._enc_algorithm(self._derive_key), self._enc_mode(self._iv), backend=self._backend).encryptor()
cipher_text = encryptor.update(plain_text) + encryptor.finalize()
return cipher_text
[docs] def decrypt(self, cipher_text):
padder = padding.PKCS7(self._hash_algorithm.block_size).padder()
cipher_text = padder.update(cipher_text) + padder.finalize()
decryptor = Cipher(self._enc_algorithm(self._derive_key), self._enc_mode(self._iv), backend=self._backend).decryptor()
plain_text = decryptor.update(cipher_text) + decryptor.finalize()
return plain_text