1

I am using the following code to create hash for a message:

from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_PSS

msg = b'\x15\x00\xea'
hash = SHA256.new(msg)
...
signer = PKCS1_PSS.new(privKey)
signature = signer.sign(hash)

However, I need that the hash digest value i.e hash.digest() to be in big-endian format. Please note that I need to pass the whole hash to the signer. I can get the hash.digest() and convert it to big-endian, but then I need to construct a variable of type hash to pass to the signer.sig() function. How can I do that?

Akshat Mahajan
  • 9,543
  • 4
  • 35
  • 44
TJ1
  • 7,578
  • 19
  • 76
  • 119
  • [Convert string from big-endian to little-endian or vice versa in Python](https://stackoverflow.com/questions/46109815/convert-string-from-big-endian-to-little-endian-or-vice-versa-in-python/46109871) – Sergey Zaykov Aug 17 '21 at 21:33
  • @СергейЗайков but then I need to reconstruct `hash` to pass to the signer, how can I do that? – TJ1 Aug 17 '21 at 21:40
  • Why do you want to do that? – Kelly Bundy Aug 17 '21 at 22:14
  • @KellyBundy do you know of a better way to create hash with big-endian digest? – TJ1 Aug 17 '21 at 22:17
  • Not sure what you're saying. Presumably what that library does is the right thing, I'm asking why you want to do a different thing. – Kelly Bundy Aug 17 '21 at 22:19
  • doing the right thing is relative, for my purpose I need it to be big-endian. – TJ1 Aug 17 '21 at 22:21
  • 1
    I'm not sure if you really need to invert the byte order of the hash (consider also the answer). But let's assume yes, then technically you need a method that processes the hash instead of the data (i.e. does not hash). _PyCryptodome_ does not support this as far as I know. An alternative is _Cryptography_ with the `Prehashed` construct, see [here, 2. snippet](https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/?highlight=pkcs1%20v1.5%20signing#signing). The inversion can be done e.g. with `bytearray#reverse()`. – Topaco Aug 18 '21 at 07:52
  • @Topaco thanks, this is a good answer. If it was an answer I would accept this. – TJ1 Aug 18 '21 at 16:46
  • Sure. I have put my comment as an answer. – Topaco Aug 18 '21 at 19:10

2 Answers2

1

The SHA-256 standard uses 32 bit words, and those are already specified to be big endian.

If you get 32 bit little endian words then you may need to reorder the bytes within them, but this is unlikely.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Thanks, just assume I need to re-order the bytes and create a new hash structure. How could I do that? I assume @Topaco answer should work, correct? – TJ1 Aug 18 '21 at 16:47
  • The chance that you have to reorder the bytes of the hash itself is minimal. In the end, SHA-256 generates bits, not a number, as it is build from bit operations rather than 512 bit calculations. If you need to build 32 bit words then you can shift the bits into place using a left-shift (`<<`), e.g. `bytevalue << 24` for the most significant bits in the word. If you do need to reorder the entire hash value, sure, follow's Topaco's answer. – Maarten Bodewes Aug 18 '21 at 21:12
1

As mentioned in my comment, I'm not sure if it is really required to invert the hash.

But if so, you need an implementation that does not process the raw data, but the already hashed data (and does not hash anymore).

PyCryptodome does not support this, but the Cryptography library with the Prehashed class does, s. here, 2nd snippet.

The following code performs a signing with PKCS#1 v1.5 and:

  • with PyCryptodome (implicit hashing)
  • with Cryptography (implicit hashing)
  • with Cryptography (using the Prehashed class and explicit hashing)
  • with Cryptography (using the Prehashed class and explicit hashing) and using the inverted hash
# Short key only for testing purposes!
pkcs8 = '''-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----'''
message = b'The quick brown fox jumps over the lazy dog'

# 1. PyCryptodome (implicit hashing)
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA

privateKey = RSA.import_key(pkcs8)
digest = SHA256.new(message)
signature = pkcs1_15.new(privateKey).sign(digest)
print(signature.hex())

# 2. Cryptography (implicit hashing)
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization

privateKey = serialization.load_pem_private_key(
    pkcs8.encode('utf8'),
    password=None,
)
digest = hashes.SHA256()
signature = privateKey.sign(
    message,
    padding.PKCS1v15(),
    digest
)
print(signature.hex())

# 3. Cryptography (explicit hashing)
from cryptography.hazmat.primitives.asymmetric import utils

digest = hashes.SHA256()
hasher = hashes.Hash(digest)
hasher.update(message)
hash = hasher.finalize()
signature = privateKey.sign(
     hash,
     padding.PKCS1v15(),     
     utils.Prehashed(digest) # The digest must still be specified because the digest ID is included in the signature.
)
print(signature.hex())

# 4. Cryptography (explicit hashing), inverse hash
hashBA  = bytearray(hash)
hashBA.reverse()
hashReversed = bytes(hashBA)
signature = privateKey.sign(
    hashReversed,
    padding.PKCS1v15(),     
    utils.Prehashed(digest) # The digest must still be specified because the digest ID is included in the signature.
)
print(signature.hex())

with the output:

8c83cad897eda249fec9eba231061d585dafc99177267e3e71bb8a3fce07cc6663bf4df7af2e1c1945d2a6bb42eb25f042228b591fc18cda82d92caae844670c
8c83cad897eda249fec9eba231061d585dafc99177267e3e71bb8a3fce07cc6663bf4df7af2e1c1945d2a6bb42eb25f042228b591fc18cda82d92caae844670c
8c83cad897eda249fec9eba231061d585dafc99177267e3e71bb8a3fce07cc6663bf4df7af2e1c1945d2a6bb42eb25f042228b591fc18cda82d92caae844670c
550ba1cd3c968fa1e24b79a939edb6740b63d2ab021fe87f0639ce978d5127792661e83f4e8fdff8124a12fe208bd70bdca9db2b9c82306f2ed018ab06363c9e

Since the PKCS#1 v1.5 signature is deterministic, cases 1 to 3 produce the same signature.

Topaco
  • 40,594
  • 4
  • 35
  • 62