RSA Signing Link to heading
RFV8017 defines the standard for PKCS RSA which is an update over 1.5 version from 2003. In that spec, RSASSA-PSS
algorithm is defines as:
- Encoding: converting the message to encoded message
- Covert encoded message to octet string
- Octet to Integer
- Sign Octet with private key
- convert signature from int to Octet
This is the copy from the [RFV8017][spec]
8.1.1. Signature Generation Operation
RSASSA-PSS-SIGN (K, M)
Input:
K signer's RSA private key M message to be signed, an octet string
Output:
S signature, an octet string of length k, where k is the length in octets of the RSA modulus n
Steps:
1. EMSA-PSS encoding: Apply the EMSA-PSS encoding operation (Section 9.1.1) to the message M to produce an encoded message EM of length \ceil ((modBits - 1)/8) octets such that the bit length of the integer OS2IP (EM) (see Section 4.2) is at most modBits - 1, where modBits is the length in bits of the RSA modulus n: EM = EMSA-PSS-ENCODE (M, modBits - 1). Note that the octet length of EM will be one less than k if modBits - 1 is divisible by 8 and equal to k otherwise. If the encoding operation outputs "message too long", output "message too long" and stop. If the encoding operation outputs "encoding error", output "encoding error" and stop. 2. RSA signature: a. Convert the encoded message EM to an integer message representative m (see Section 4.2): m = OS2IP (EM). b. Apply the RSASP1 signature primitive (Section 5.2.1) to the RSA private key K and the message representative m to produce an integer signature representative s: s = RSASP1 (K, m). c. Convert the signature representative s to a signature S of length k octets (see Section 4.1): S = I2OSP (s, k). 3. Output the signature S.
Python implementation Link to heading
This is the implementation from [github which is extacly the same as the algorithms specified
def sign(private_key, message,
emsa_pss_encode=emsa_pss.encode,
hash_class=hashlib.sha1,
mgf1=mgf.mgf1,
rnd=default_crypto_random):
'''Sign message using private_key and the PKCS#1 2.0 RSASSA-PSS
algorithm.
private_key - the private key to use
message - the byte string to sign
emsa_pss_encode - the encoding to use, default to EMSA-PSS encoding
hash_class - the hash algorithme to use, default to SHA-1 from the
Python hashlib package.
mgf1 - the mask generating function to use, default to MGF1
rnd - a random number generator to use for the PSS encoding,
default to a Python SystemRandom instance.
'''
mod_bits = private_key.bit_size
embits = mod_bits - 1
em = emsa_pss_encode(message, embits, hash_class=hash_class,
mgf=mgf1, rnd=rnd)
m = primitives.os2ip(em)
s = private_key.rsasp1(m)
return primitives.i2osp(s, private_key.byte_size)
def encode(m, embits, hash_class=hashlib.sha1,
mgf=mgf.mgf1, salt=None, s_len=None, rnd=default_crypto_random):
'''Encode a message using the PKCS v2 PSS padding.
m - the message to encode
embits - the length of the padded message
mgf - a masg generating function, default is mgf1 the mask generating
function proposed in the PKCS#1 v2 standard.
hash_class - the hash algorithm to use to compute the digest of the
message, must conform to the hashlib class interface.
salt - a fixed salt string to use, if None, a random string of length
s_len is used instead, necessary for tests,
s_len - the length of the salt string when using a random generator to
create it, if None the length of the digest is used.
rnd - the random generator used to compute the salt string
Return value: the padded message
'''
m_hash = hash_class(m).digest()
h_len = len(m_hash)
if salt is not None:
s_len = len(salt)
else:
if s_len is None:
s_len = h_len
salt = primitives.i2osp(rnd.getrandbits(s_len*8), s_len)
em_len = primitives.integer_ceil(embits, 8)
if em_len < len(m_hash) + s_len + 2:
raise exceptions.EncodingError
m_prime = (b'\x00' * 8) + m_hash + salt
h = hash_class(m_prime).digest()
ps = b'\x00' * (em_len - s_len - h_len - 2)
db = ps + b'\x01' + salt
db_mask = mgf(h, em_len - h_len - 1)
masked_db = primitives.string_xor(db, db_mask)
octets, bits = (8 * em_len - embits) // 8, (8*em_len-embits) % 8
# replace first `octets' bytes
masked_db = (b'\x00' * octets) + masked_db[octets:]
new_byte = _and_byte(masked_db[octets], 255 >> bits)
masked_db = masked_db[:octets] + new_byte + masked_db[octets+1:]
return masked_db + h + b'\xbc'
def i2osp(x, x_len):
'''Converts the integer x to its big-endian representation of length
x_len.
'''
if x > 256**x_len:
raise exceptions.IntegerTooLarge
h = hex(x)[2:]
if h[-1] == 'L':
h = h[:-1]
if len(h) & 1 == 1:
h = '0%s' % h
x = binascii.unhexlify(h)
return b'\x00' * int(x_len-len(x)) + x
def os2ip(x):
'''Converts the byte string x representing an integer reprented using the
big-endian convient to an integer.
'''
h = binascii.hexlify(x)
return int(h, 16)
class RsaPrivateKey(object):
__slots__ = ('n', 'd', 'bit_size', 'byte_size')
def __init__(self, n, d):
self.n = n
self.d = d
self.bit_size = primitives.integer_bit_size(n)
self.byte_size = primitives.integer_byte_size(n)
def __repr__(self):
return '<RsaPrivateKey n: %d d: %d bit_size: %d>' % (self.n, self.d, self.bit_size)
def rsadp(self, c):
if not (0 <= c <= self.n-1):
raise exceptions.CiphertextRepresentativeOutOfRange
return primitives._pow(c, self.d, self.n)
def rsasp1(self, m):
if not (0 <= m <= self.n-1):
raise exceptions.MessageRepresentativeOutOfRange
return self.rsadp(m)