Skip to content

Commit 5da8f81

Browse files
committed
Merge pull request #2 from Rican7/bugfix/ntlmv1-des-key-expansion
Bugfix - NTLMv1 DES key expansion
2 parents 10e4c1e + bd9749b commit 5da8f81

5 files changed

Lines changed: 144 additions & 73 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
/**
3+
* Robin NTLM
4+
*
5+
* @copyright 2015 Robin Powered, Inc.
6+
* @link https://robinpowered.com/
7+
*/
8+
9+
namespace Robin\Ntlm\Crypt\Des;
10+
11+
use SplFixedArray;
12+
13+
/**
14+
* {@inheritDoc}
15+
*
16+
* Provides an abstraction for common NTLM required operations, such as key
17+
* expansion and normalization.
18+
*/
19+
abstract class AbstractDesEncrypter implements DesEncrypterInterface
20+
{
21+
22+
/**
23+
* Properties
24+
*/
25+
26+
/**
27+
* Whether or not to expand and normalize the key before encrypting.
28+
*
29+
* @type bool
30+
*/
31+
private $expand_and_normalize_keys;
32+
33+
34+
/**
35+
* Methods
36+
*/
37+
38+
/**
39+
* Constructor
40+
*
41+
* @param bool $expand_and_normalize_keys Whether or not to expand and
42+
* normalize the key before encrypting.
43+
*/
44+
public function __construct($expand_and_normalize_keys = true)
45+
{
46+
$this->expand_and_normalize_keys = $expand_and_normalize_keys;
47+
}
48+
49+
/**
50+
* Process a key for DES encryption.
51+
*
52+
* Optionally performs an expansion and normalization process to the key.
53+
*
54+
* @param string $raw_key The raw key.
55+
* @return string The processed key.
56+
*/
57+
protected function processKey($raw_key)
58+
{
59+
$key = $raw_key;
60+
61+
if ($this->expand_and_normalize_keys) {
62+
$key = self::expand56BitKeyTo64BitKey($key, true);
63+
}
64+
65+
return $key;
66+
}
67+
68+
/**
69+
* Expands a 56-bit key to a full 64-bit key for DES encryption.
70+
*
71+
* @link http://php.net/manual/en/ref.hash.php#84587 Implementation basis.
72+
* @link https://github.com/jclulow/node-smbhash/blob/edc48e2b/lib/common.js
73+
* Inspired by Joshua Clulow's work.
74+
* @param string $string_key The 56-bit key to expand.
75+
* @param bool $set_parity Whether or not to set parity for each byte.
76+
* @return string The expanded key.
77+
*/
78+
private static function expand56BitKeyTo64BitKey($string_key, $set_parity = true)
79+
{
80+
$byte_array_56 = new SplFixedArray(7);
81+
$byte_array_64 = new SplFixedArray(8);
82+
$key_64bit = '';
83+
84+
// Get the byte value of each ASCII character in the string
85+
for ($i = 0; $i < $byte_array_56->getSize(); $i++) {
86+
$byte_array_56[$i] = isset($string_key[$i]) ? ord($string_key[$i]) : 0;
87+
}
88+
89+
$byte_array_64[0] = $byte_array_56[0] & 254;
90+
$byte_array_64[1] = ($byte_array_56[0] << 7) | ($byte_array_56[1] >> 1);
91+
$byte_array_64[2] = ($byte_array_56[1] << 6) | ($byte_array_56[2] >> 2);
92+
$byte_array_64[3] = ($byte_array_56[2] << 5) | ($byte_array_56[3] >> 3);
93+
$byte_array_64[4] = ($byte_array_56[3] << 4) | ($byte_array_56[4] >> 4);
94+
$byte_array_64[5] = ($byte_array_56[4] << 3) | ($byte_array_56[5] >> 5);
95+
$byte_array_64[6] = ($byte_array_56[5] << 2) | ($byte_array_56[6] >> 6);
96+
$byte_array_64[7] = $byte_array_56[6] << 1;
97+
98+
foreach ($byte_array_64 as $byte_val) {
99+
// Optionally set parity for each byte
100+
$byte_val = $set_parity ? self::setParityBit($byte_val) : $byte_val;
101+
102+
$key_64bit .= chr($byte_val);
103+
}
104+
105+
return $key_64bit;
106+
}
107+
108+
/**
109+
* Set an odd parity bit for a given byte, in least-significant position.
110+
*
111+
* @link https://github.com/jclulow/node-smbhash/blob/edc48e2b/lib/common.js
112+
* Implementation basis.
113+
* @param int $byte An 8-bit byte value.
114+
* @return int An 8-bit byte value.
115+
*/
116+
private static function setParityBit($byte)
117+
{
118+
$parity = 1;
119+
120+
for ($i = 1; $i < 8; $i++) {
121+
$parity = ($parity + (($byte >> $i) & 1)) %2;
122+
}
123+
124+
$byte = $byte | ($parity & 1);
125+
126+
return $byte;
127+
}
128+
}

src/Robin/Ntlm/Crypt/Des/McryptDesEncrypter.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@
1111
use InvalidArgumentException;
1212
use Robin\Ntlm\Crypt\CipherMode;
1313
use Robin\Ntlm\Crypt\Exception\CryptographicFailureException;
14-
use UnexpectedValueException;
1514

1615
/**
1716
* An engine used to encrypt data using the DES standard algorithm and
1817
* implemented using the PHP "mcrypt" extension.
1918
*
2019
* @link http://php.net/mcrypt
2120
*/
22-
class McryptDesEncrypter implements DesEncrypterInterface
21+
class McryptDesEncrypter extends AbstractDesEncrypter implements DesEncrypterInterface
2322
{
2423

2524
/**
@@ -52,6 +51,8 @@ public function encrypt($key, $data, $mode, $initialization_vector)
5251
throw new InvalidArgumentException('Unknown cipher mode "'. $mode .'"');
5352
}
5453

54+
$key = $this->processKey($key);
55+
5556
$encrypted = mcrypt_encrypt(MCRYPT_DES, $key, $data, $mode, $initialization_vector);
5657

5758
if (false === $encrypted) {

src/Robin/Ntlm/Crypt/Des/OpenSslDesEncrypter.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
* @link http://php.net/openssl
2020
*/
21-
class OpenSslDesEncrypter implements DesEncrypterInterface
21+
class OpenSslDesEncrypter extends AbstractDesEncrypter implements DesEncrypterInterface
2222
{
2323

2424
/**
@@ -65,9 +65,13 @@ class OpenSslDesEncrypter implements DesEncrypterInterface
6565
*
6666
* @param bool $zero_pad Whether or not to zero-byte pad the data before
6767
* encrypting for some cipher modes.
68+
* @param bool $expand_and_normalize_keys Whether or not to expand and
69+
* normalize the key before encrypting.
6870
*/
69-
public function __construct($zero_pad = true)
71+
public function __construct($zero_pad = true, $expand_and_normalize_keys = true)
7072
{
73+
parent::__construct($expand_and_normalize_keys);
74+
7175
$this->zero_pad = $zero_pad;
7276
}
7377

@@ -84,6 +88,8 @@ public function encrypt($key, $data, $mode, $initialization_vector)
8488

8589
$options = $this->getOpenSslEncryptionOptions();
8690

91+
$key = $this->processKey($key);
92+
8793
$encrypted = openssl_encrypt($data, $mode, $key, $options, $initialization_vector);
8894

8995
if (false === $encrypted) {

src/Robin/Ntlm/Hasher/LmHasher.php

Lines changed: 4 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
use Robin\Ntlm\Credential\Password;
1414
use Robin\Ntlm\Crypt\CipherMode;
1515
use Robin\Ntlm\Crypt\Des\DesEncrypterInterface;
16-
use Robin\Ntlm\Crypt\Random\RandomByteGeneratorInterface;
17-
use SplFixedArray;
1816

1917
/**
2018
* Uses the "LM hash" computation strategy to hash a {@link Password} credential
@@ -53,14 +51,6 @@ class LmHasher implements HasherInterface
5351
*/
5452
const PASSWORD_SLICE_LENGTH = 7;
5553

56-
/**
57-
* The length of the randomly generated binary string used for generating
58-
* each piece of the resulting hash.
59-
*
60-
* @type int
61-
*/
62-
const RANDOM_BINARY_STRING_LENGTH = 8;
63-
6454
/**
6555
* The constant known ASCII text to encrypt with the generated keys.
6656
*
@@ -83,14 +73,6 @@ class LmHasher implements HasherInterface
8373
*/
8474
private $des_encrypter;
8575

86-
/**
87-
* The generator used to generate cryptographically secure random bytes to
88-
* provide an initialization vector for encryption.
89-
*
90-
* @type RandomByteGeneratorInterface
91-
*/
92-
private $random_byte_generator;
93-
9476

9577
/**
9678
* Methods
@@ -101,16 +83,10 @@ class LmHasher implements HasherInterface
10183
*
10284
* @param DesEncrypterInterface $des_encrypter The DES encryption engine
10385
* used to generate the hash.
104-
* @param RandomByteGeneratorInterface $random_byte_generator Used to
105-
* generate cryptographically secure random bytes to provide an
106-
* initialization vector for encryption.
10786
*/
108-
public function __construct(
109-
DesEncrypterInterface $des_encrypter,
110-
RandomByteGeneratorInterface $random_byte_generator
111-
) {
87+
public function __construct(DesEncrypterInterface $des_encrypter)
88+
{
11289
$this->des_encrypter = $des_encrypter;
113-
$this->random_byte_generator = $random_byte_generator;
11490
}
11591

11692
/**
@@ -134,53 +110,16 @@ public function hash(Password $password)
134110
$binary_hash = array_reduce(
135111
$halves,
136112
function ($result, $half) {
137-
$expanded = static::expand56BitKeyTo64BitKey($half);
138-
139113
return $result . $this->des_encrypter->encrypt(
140-
$expanded,
114+
$half,
141115
static::ENCRYPT_DATA_CONSTANT,
142116
CipherMode::ECB,
143-
$this->random_byte_generator->generate(static::RANDOM_BINARY_STRING_LENGTH)
117+
'' // DES-ECB expects a 0-byte-length initialization vector
144118
);
145119
},
146120
''
147121
);
148122

149123
return Hash::fromBinaryString($binary_hash, HashType::LM);
150124
}
151-
152-
/**
153-
* Expands a 56-bit key to a full 64-bit key for DES encryption.
154-
*
155-
* @link http://php.net/manual/en/ref.hash.php#84587 Implementation basis.
156-
* @link https://github.com/jclulow/node-smbhash/blob/edc48e2b93067/lib/common.js Inspired by Joshua Clulow's work.
157-
* @param string $string_key The 56-bit key to expand
158-
* @return string
159-
*/
160-
public static function expand56BitKeyTo64BitKey($string_key)
161-
{
162-
$byte_array_56 = new SplFixedArray(7);
163-
$byte_array_64 = new SplFixedArray(8);
164-
$key_64bit = '';
165-
166-
// Get the byte value of each ASCII character in the string
167-
for ($i = 0; $i < $byte_array_56->getSize(); $i++) {
168-
$byte_array_56[$i] = isset($string_key[$i]) ? ord($string_key[$i]) : 0;
169-
}
170-
171-
$byte_array_64[0] = $byte_array_56[0] & 254;
172-
$byte_array_64[1] = ($byte_array_56[0] << 7) | ($byte_array_56[1] >> 1);
173-
$byte_array_64[2] = ($byte_array_56[1] << 6) | ($byte_array_56[2] >> 2);
174-
$byte_array_64[3] = ($byte_array_56[2] << 5) | ($byte_array_56[3] >> 3);
175-
$byte_array_64[4] = ($byte_array_56[3] << 4) | ($byte_array_56[4] >> 4);
176-
$byte_array_64[5] = ($byte_array_56[4] << 3) | ($byte_array_56[5] >> 5);
177-
$byte_array_64[6] = ($byte_array_56[5] << 2) | ($byte_array_56[6] >> 6);
178-
$byte_array_64[7] = $byte_array_56[6] << 1;
179-
180-
foreach ($byte_array_64 as $byte_val) {
181-
$key_64bit .= chr($byte_val);
182-
}
183-
184-
return $key_64bit;
185-
}
186125
}

src/Robin/Ntlm/Message/NtlmV1AuthenticateMessageEncoder.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,14 +322,11 @@ public function calculateChallengeResponseData(HashCredentialInterface $hash_cre
322322
$binary_data = array_reduce(
323323
$key_blocks,
324324
function ($result, $key_block) use ($data) {
325-
// Generate an initialization vector equal to the length of the nonce
326-
$initialization_vector = $this->random_byte_generator->generate(strlen($data));
327-
328325
return $result . $this->des_encrypter->encrypt(
329326
$key_block,
330327
$data,
331328
CipherMode::ECB,
332-
$initialization_vector
329+
'' // DES-ECB expects a 0-byte-length initialization vector
333330
);
334331
},
335332
''

0 commit comments

Comments
 (0)