mirror of
https://github.com/MikroWizard/mikroman.git
synced 2025-07-10 14:04:29 +02:00
272 lines
8.4 KiB
Python
272 lines
8.4 KiB
Python
|
from . import mschap
|
||
|
import hashlib
|
||
|
import random
|
||
|
|
||
|
SHSpad1 = \
|
||
|
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + \
|
||
|
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + \
|
||
|
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + \
|
||
|
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||
|
|
||
|
SHSpad2 = \
|
||
|
b"\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2" + \
|
||
|
b"\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2" + \
|
||
|
b"\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2" + \
|
||
|
b"\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2"
|
||
|
|
||
|
Magic1 = \
|
||
|
b"\x54\x68\x69\x73\x20\x69\x73\x20\x74" + \
|
||
|
b"\x68\x65\x20\x4d\x50\x50\x45\x20\x4d" + \
|
||
|
b"\x61\x73\x74\x65\x72\x20\x4b\x65\x79"
|
||
|
|
||
|
Magic2 = \
|
||
|
b"\x4f\x6e\x20\x74\x68\x65\x20\x63\x6c\x69" + \
|
||
|
b"\x65\x6e\x74\x20\x73\x69\x64\x65\x2c\x20" + \
|
||
|
b"\x74\x68\x69\x73\x20\x69\x73\x20\x74\x68" + \
|
||
|
b"\x65\x20\x73\x65\x6e\x64\x20\x6b\x65\x79" + \
|
||
|
b"\x3b\x20\x6f\x6e\x20\x74\x68\x65\x20\x73" + \
|
||
|
b"\x65\x72\x76\x65\x72\x20\x73\x69\x64\x65" + \
|
||
|
b"\x2c\x20\x69\x74\x20\x69\x73\x20\x74\x68" + \
|
||
|
b"\x65\x20\x72\x65\x63\x65\x69\x76\x65\x20" + \
|
||
|
b"\x6b\x65\x79\x2e"
|
||
|
|
||
|
Magic3 = \
|
||
|
b"\x4f\x6e\x20\x74\x68\x65\x20\x63\x6c\x69" + \
|
||
|
b"\x65\x6e\x74\x20\x73\x69\x64\x65\x2c\x20" + \
|
||
|
b"\x74\x68\x69\x73\x20\x69\x73\x20\x74\x68" + \
|
||
|
b"\x65\x20\x72\x65\x63\x65\x69\x76\x65\x20" + \
|
||
|
b"\x6b\x65\x79\x3b\x20\x6f\x6e\x20\x74\x68" + \
|
||
|
b"\x65\x20\x73\x65\x72\x76\x65\x72\x20\x73" + \
|
||
|
b"\x69\x64\x65\x2c\x20\x69\x74\x20\x69\x73" + \
|
||
|
b"\x20\x74\x68\x65\x20\x73\x65\x6e\x64\x20" + \
|
||
|
b"\x6b\x65\x79\x2e"
|
||
|
|
||
|
|
||
|
def mppe_chap2_gen_keys(password, nt_response,nthash=False):
|
||
|
"""
|
||
|
3.3. Generating 128-bit Session Keys
|
||
|
|
||
|
When used in conjunction with MS-CHAP-2 authentication, the initial
|
||
|
MPPE session keys are derived from the peer's Windows NT password.
|
||
|
|
||
|
The first step is to obfuscate the peer's password using
|
||
|
NtPasswordHash() function as described in [8].
|
||
|
|
||
|
NtPasswordHash(Password, PasswordHash)
|
||
|
|
||
|
The first 16 octets of the result are then hashed again using the MD4
|
||
|
algorithm.
|
||
|
|
||
|
PasswordHashHash = md4(PasswordHash)
|
||
|
|
||
|
The first 16 octets of this second hash are used together with the
|
||
|
NT-Response field from the MS-CHAP-2 Response packet [8] as the basis
|
||
|
for the master session key:
|
||
|
|
||
|
GetMasterKey(PasswordHashHash, NtResponse, MasterKey)
|
||
|
|
||
|
Once the master key has been generated, it is used to derive two
|
||
|
128-bit master session keys, one for sending and one for receiving:
|
||
|
|
||
|
GetAsymmetricStartKey(MasterKey, MasterSendKey, 16, TRUE, TRUE)
|
||
|
GetAsymmetricStartKey(MasterKey, MasterReceiveKey, 16, FALSE, TRUE)
|
||
|
|
||
|
The master session keys are never used to encrypt or decrypt data;
|
||
|
they are only used in the derivation of transient session keys. The
|
||
|
initial transient session keys are obtained by calling the function
|
||
|
GetNewKeyFromSHA() (described in [3]):
|
||
|
|
||
|
GetNewKeyFromSHA(MasterSendKey, MasterSendKey, 16, SendSessionKey)
|
||
|
GetNewKeyFromSHA(MasterReceiveKey, MasterReceiveKey, 16,
|
||
|
ReceiveSessionKey)
|
||
|
|
||
|
Finally, the RC4 tables are initialized using the new session keys:
|
||
|
|
||
|
rc4_key(SendRC4key, 16, SendSessionKey)
|
||
|
rc4_key(ReceiveRC4key, 16, ReceiveSessionKey)
|
||
|
"""
|
||
|
if nthash:
|
||
|
password_hash=bytes.fromhex(nthash).decode('iso8859-1',errors='ignore')
|
||
|
else:
|
||
|
password_hash = mschap.nt_password_hash(password)
|
||
|
password_hash_hash = mschap.hash_nt_password_hash(password_hash).encode()
|
||
|
master_key = get_master_key(password_hash_hash, nt_response)
|
||
|
master_send_key = get_asymetric_start_key(master_key, 16, True, True)
|
||
|
master_recv_key = get_asymetric_start_key(master_key, 16, False, True)
|
||
|
return master_send_key, master_recv_key
|
||
|
|
||
|
|
||
|
def get_master_key(password_hash_hash, nt_response):
|
||
|
"""
|
||
|
GetMasterKey(
|
||
|
IN 16-octet PasswordHashHash,
|
||
|
IN 24-octet NTResponse,
|
||
|
OUT 16-octet MasterKey )
|
||
|
{
|
||
|
20-octet Digest
|
||
|
|
||
|
ZeroMemory(Digest, sizeof(Digest));
|
||
|
|
||
|
/*
|
||
|
* SHSInit(), SHSUpdate() and SHSFinal()
|
||
|
* are an implementation of the Secure Hash Standard [7].
|
||
|
*/
|
||
|
|
||
|
SHSInit(Context);
|
||
|
SHSUpdate(Context, PasswordHashHash, 16);
|
||
|
SHSUpdate(Context, NTResponse, 24);
|
||
|
SHSUpdate(Context, Magic1, 27);
|
||
|
SHSFinal(Context, Digest);
|
||
|
|
||
|
MoveMemory(MasterKey, Digest, 16);
|
||
|
}
|
||
|
|
||
|
"""
|
||
|
sha_hash = hashlib.sha1()
|
||
|
sha_hash.update(password_hash_hash)
|
||
|
sha_hash.update(nt_response)
|
||
|
sha_hash.update(Magic1)
|
||
|
return sha_hash.digest()[:16]
|
||
|
|
||
|
|
||
|
def get_asymetric_start_key(master_key, session_key_length, is_send, is_server):
|
||
|
"""
|
||
|
|
||
|
VOID
|
||
|
GetAsymetricStartKey(
|
||
|
IN 16-octet MasterKey,
|
||
|
OUT 8-to-16 octet SessionKey,
|
||
|
IN INTEGER SessionKeyLength,
|
||
|
IN BOOLEAN IsSend,
|
||
|
IN BOOLEAN IsServer )
|
||
|
{
|
||
|
|
||
|
20-octet Digest;
|
||
|
|
||
|
ZeroMemory(Digest, 20);
|
||
|
|
||
|
if (IsSend) {
|
||
|
if (IsServer) {
|
||
|
s = Magic3
|
||
|
} else {
|
||
|
s = Magic2
|
||
|
}
|
||
|
} else {
|
||
|
if (IsServer) {
|
||
|
|
||
|
s = Magic2
|
||
|
} else {
|
||
|
s = Magic3
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* SHSInit(), SHSUpdate() and SHSFinal()
|
||
|
* are an implementation of the Secure Hash Standard [7].
|
||
|
*/
|
||
|
|
||
|
SHSInit(Context);
|
||
|
SHSUpdate(Context, MasterKey, 16);
|
||
|
SHSUpdate(Context, SHSpad1, 40);
|
||
|
SHSUpdate(Context, s, 84);
|
||
|
SHSUpdate(Context, SHSpad2, 40);
|
||
|
SHSFinal(Context, Digest);
|
||
|
|
||
|
MoveMemory(SessionKey, Digest, SessionKeyLength);
|
||
|
}
|
||
|
"""
|
||
|
if is_send:
|
||
|
if is_server:
|
||
|
s = Magic3
|
||
|
else:
|
||
|
s = Magic2
|
||
|
else:
|
||
|
if is_server:
|
||
|
s = Magic2
|
||
|
else:
|
||
|
s = Magic3
|
||
|
sha_hash = hashlib.sha1()
|
||
|
sha_hash.update(master_key)
|
||
|
sha_hash.update(SHSpad1)
|
||
|
sha_hash.update(s)
|
||
|
sha_hash.update(SHSpad2)
|
||
|
return sha_hash.digest()[:session_key_length]
|
||
|
|
||
|
|
||
|
def create_plain_text(key):
|
||
|
key_len = len(key)
|
||
|
key=key.decode(errors='ignore')
|
||
|
while (len(key) + 1) % 16: key += "\000"
|
||
|
return chr(key_len) + key
|
||
|
|
||
|
|
||
|
def create_salts():
|
||
|
send_salt = create_salt()
|
||
|
recv_salt = create_salt()
|
||
|
while send_salt == recv_salt: recv_salt = create_salt()
|
||
|
return (send_salt, recv_salt)
|
||
|
|
||
|
|
||
|
def create_salt():
|
||
|
return chr(128 + random.randrange(0, 128)) + chr(random.randrange(0, 256))
|
||
|
|
||
|
def gen_radius_encrypt_keys(send_key, recv_key, secret, request_authenticator):
|
||
|
send_salt, recv_salt = create_salts()
|
||
|
_send_key = send_salt + radius_encrypt_keys(
|
||
|
create_plain_text(send_key),
|
||
|
secret,
|
||
|
request_authenticator,
|
||
|
send_salt
|
||
|
)
|
||
|
_recv_key = recv_salt + radius_encrypt_keys(
|
||
|
create_plain_text(recv_key),
|
||
|
secret,
|
||
|
request_authenticator,
|
||
|
recv_salt
|
||
|
)
|
||
|
|
||
|
return _send_key, _recv_key
|
||
|
|
||
|
|
||
|
def radius_encrypt_keys(plain_text, secret, request_authenticator, salt):
|
||
|
"""
|
||
|
Construct a plaintext version of the String field by concate-
|
||
|
nating the Key-Length and Key sub-fields. If necessary, pad
|
||
|
the resulting string until its length (in octets) is an even
|
||
|
multiple of 16. It is recommended that zero octets (0x00) be
|
||
|
used for padding. Call this plaintext P.
|
||
|
|
||
|
Call the shared secret S, the pseudo-random 128-bit Request
|
||
|
Authenticator (from the corresponding Access-Request packet) R,
|
||
|
and the contents of the Salt field A. Break P into 16 octet
|
||
|
chunks p(1), p(2)...p(i), where i = len(P)/16. Call the
|
||
|
ciphertext blocks c(1), c(2)...c(i) and the final ciphertext C.
|
||
|
Intermediate values b(1), b(2)...c(i) are required. Encryption
|
||
|
is performed in the following manner ('+' indicates
|
||
|
concatenation):
|
||
|
|
||
|
b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
|
||
|
b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
|
||
|
. .
|
||
|
. .
|
||
|
. .
|
||
|
b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
|
||
|
|
||
|
The resulting encrypted String field will contain
|
||
|
c(1)+c(2)+...+c(i).
|
||
|
"""
|
||
|
i = int(len(plain_text) / 16)
|
||
|
b = hashlib.new("md5", secret + request_authenticator + salt.encode(errors='ignore')).digest()
|
||
|
c = xor(plain_text[:16], b)
|
||
|
result = c
|
||
|
for x in range(1, i):
|
||
|
b = hashlib.new("md5", secret + c.encode().digest())
|
||
|
c = xor(plain_text[x * 16:(x + 1) * 16], b)
|
||
|
result += c
|
||
|
return result
|
||
|
|
||
|
|
||
|
def xor(str1, str2):
|
||
|
str2=str2.decode(errors='ignore')
|
||
|
return ''.join(map(lambda s1, s2: chr(ord(s1) ^ ord(s2)), str1, str2))
|