Skip to content
This repository was archived by the owner on Apr 20, 2025. It is now read-only.

Commit d008525

Browse files
adamantikesybrenstuvel
authored andcommitted
PKCS#1 2.0: Implementation of MGF1 (#89)
Implementation of the Mask Generation Function `MGF1` used in the OAEP encoding step. For more information, the MGF1 specification is at https://tools.ietf.org/html/rfc2437#section-10.2.1
1 parent 425eb24 commit d008525

File tree

3 files changed

+195
-1
lines changed

3 files changed

+195
-1
lines changed

rsa/pkcs1.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ def sign_hash(hash_value, priv_key, hash_method):
250250
251251
Hashes the message, then signs the hash with the given key. This is known
252252
as a "detached signature", because the message itself isn't altered.
253-
253+
254254
:param hash_value: A precomputed hash to sign (ignores message). Should be set to
255255
None if needing to hash and sign message.
256256
:param priv_key: the :py:class:`rsa.PrivateKey` to sign with

rsa/pkcs1_v2.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2011 Sybren A. Stüvel <[email protected]>
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
"""Functions for PKCS#1 version 2 encryption and signing
18+
19+
This module implements certain functionality from PKCS#1 version 2. Main
20+
documentation is RFC 2437: https://tools.ietf.org/html/rfc2437
21+
"""
22+
23+
from rsa._compat import range
24+
from rsa import (
25+
common,
26+
pkcs1,
27+
transform,
28+
)
29+
30+
HASH_METHOD_TO_BYTE_LENGTH = {
31+
'MD5': 16,
32+
'SHA-1': 20,
33+
'SHA-256': 28,
34+
'SHA-384': 48,
35+
'SHA-512': 64,
36+
}
37+
38+
39+
def mgf1(seed, length, hasher='SHA-1'):
40+
"""
41+
MGF1 is a Mask Generation Function based on a hash function.
42+
43+
A mask generation function takes an octet string of variable length and a
44+
desired output length as input, and outputs an octet string of the desired
45+
length. The plaintext-awareness of RSAES-OAEP relies on the random nature of
46+
the output of the mask generation function, which in turn relies on the
47+
random nature of the underlying hash.
48+
49+
:param bytes seed: seed from which mask is generated, an octet string
50+
:param int length: intended length in octets of the mask, at most 2^32(hLen)
51+
:param str hasher: hash function (hLen denotes the length in octets of the hash
52+
function output)
53+
54+
:return: mask, an octet string of length `length`
55+
:rtype: bytes
56+
57+
:raise OverflowError: when `length` is too large for the specified `hasher`
58+
:raise ValueError: when specified `hasher` is invalid
59+
"""
60+
61+
try:
62+
hash_length = HASH_METHOD_TO_BYTE_LENGTH[hasher]
63+
except KeyError:
64+
raise ValueError(
65+
'Invalid `hasher` specified. Please select one of: {hash_list}'.format(
66+
hash_list=', '.join(sorted(HASH_METHOD_TO_BYTE_LENGTH.keys()))
67+
)
68+
)
69+
70+
# If l > 2^32(hLen), output "mask too long" and stop.
71+
if length > (2**32 * hash_length):
72+
raise OverflowError(
73+
"Desired length should be at most 2**32 times the hasher's output "
74+
"length ({hash_length} for {hasher} function)".format(
75+
hash_length=hash_length,
76+
hasher=hasher,
77+
)
78+
)
79+
80+
# Looping `counter` from 0 to ceil(l / hLen)-1, build `output` based on the
81+
# hashes formed by (`seed` + C), being `C` an octet string of length 4
82+
# generated by converting `counter` with the primitive I2OSP
83+
output = b''.join(
84+
pkcs1.compute_hash(
85+
seed + transform.int2bytes(counter, fill_size=4),
86+
method_name=hasher,
87+
)
88+
for counter in range(common.ceil_div(length, hash_length) + 1)
89+
)
90+
91+
# Output the leading `length` octets of `output` as the octet string mask.
92+
return output[:length]
93+
94+
95+
__all__ = [
96+
'mgf1',
97+
]
98+
99+
if __name__ == '__main__':
100+
print('Running doctests 1000x or until failure')
101+
import doctest
102+
103+
for count in range(1000):
104+
(failures, tests) = doctest.testmod()
105+
if failures:
106+
break
107+
108+
if count and count % 100 == 0:
109+
print('%i times' % count)
110+
111+
print('Doctests done')

tests/test_pkcs1_v2.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2011 Sybren A. Stüvel <[email protected]>
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
"""Tests PKCS #1 version 2 functionality.
18+
19+
Most of the mocked values come from the test vectors found at:
20+
http://www.itomorrowmag.com/emc-plus/rsa-labs/standards-initiatives/pkcs-rsa-cryptography-standard.htm
21+
"""
22+
23+
import unittest
24+
25+
from rsa import pkcs1_v2
26+
27+
28+
class MGFTest(unittest.TestCase):
29+
def test_oaep_int_db_mask(self):
30+
seed = (
31+
b'\xaa\xfd\x12\xf6\x59\xca\xe6\x34\x89\xb4\x79\xe5\x07\x6d\xde\xc2'
32+
b'\xf0\x6c\xb5\x8f'
33+
)
34+
db = (
35+
b'\xda\x39\xa3\xee\x5e\x6b\x4b\x0d\x32\x55\xbf\xef\x95\x60\x18\x90'
36+
b'\xaf\xd8\x07\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
37+
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
38+
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
39+
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
40+
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xd4\x36\xe9\x95\x69'
41+
b'\xfd\x32\xa7\xc8\xa0\x5b\xbc\x90\xd3\x2c\x49'
42+
)
43+
masked_db = (
44+
b'\xdc\xd8\x7d\x5c\x68\xf1\xee\xa8\xf5\x52\x67\xc3\x1b\x2e\x8b\xb4'
45+
b'\x25\x1f\x84\xd7\xe0\xb2\xc0\x46\x26\xf5\xaf\xf9\x3e\xdc\xfb\x25'
46+
b'\xc9\xc2\xb3\xff\x8a\xe1\x0e\x83\x9a\x2d\xdb\x4c\xdc\xfe\x4f\xf4'
47+
b'\x77\x28\xb4\xa1\xb7\xc1\x36\x2b\xaa\xd2\x9a\xb4\x8d\x28\x69\xd5'
48+
b'\x02\x41\x21\x43\x58\x11\x59\x1b\xe3\x92\xf9\x82\xfb\x3e\x87\xd0'
49+
b'\x95\xae\xb4\x04\x48\xdb\x97\x2f\x3a\xc1\x4f\x7b\xc2\x75\x19\x52'
50+
b'\x81\xce\x32\xd2\xf1\xb7\x6d\x4d\x35\x3e\x2d'
51+
)
52+
53+
# dbMask = MGF(seed, length(DB))
54+
db_mask = pkcs1_v2.mgf1(seed, length=len(db))
55+
expected_db_mask = (
56+
b'\x06\xe1\xde\xb2\x36\x9a\xa5\xa5\xc7\x07\xd8\x2c\x8e\x4e\x93\x24'
57+
b'\x8a\xc7\x83\xde\xe0\xb2\xc0\x46\x26\xf5\xaf\xf9\x3e\xdc\xfb\x25'
58+
b'\xc9\xc2\xb3\xff\x8a\xe1\x0e\x83\x9a\x2d\xdb\x4c\xdc\xfe\x4f\xf4'
59+
b'\x77\x28\xb4\xa1\xb7\xc1\x36\x2b\xaa\xd2\x9a\xb4\x8d\x28\x69\xd5'
60+
b'\x02\x41\x21\x43\x58\x11\x59\x1b\xe3\x92\xf9\x82\xfb\x3e\x87\xd0'
61+
b'\x95\xae\xb4\x04\x48\xdb\x97\x2f\x3a\xc1\x4e\xaf\xf4\x9c\x8c\x3b'
62+
b'\x7c\xfc\x95\x1a\x51\xec\xd1\xdd\xe6\x12\x64'
63+
)
64+
65+
self.assertEqual(db_mask, expected_db_mask)
66+
67+
# seedMask = MGF(maskedDB, length(seed))
68+
seed_mask = pkcs1_v2.mgf1(masked_db, length=len(seed))
69+
expected_seed_mask = (
70+
b'\x41\x87\x0b\x5a\xb0\x29\xe6\x57\xd9\x57\x50\xb5\x4c\x28\x3c\x08'
71+
b'\x72\x5d\xbe\xa9'
72+
)
73+
74+
self.assertEqual(seed_mask, expected_seed_mask)
75+
76+
def test_invalid_hasher(self):
77+
"""Tests an invalid hasher generates an exception"""
78+
with self.assertRaises(ValueError):
79+
pkcs1_v2.mgf1(b'\x06\xe1\xde\xb2', length=8, hasher='SHA2')
80+
81+
def test_invalid_length(self):
82+
with self.assertRaises(OverflowError):
83+
pkcs1_v2.mgf1(b'\x06\xe1\xde\xb2', length=2**50)

0 commit comments

Comments
 (0)