The question is not silly at all. Asymmetric cryptography is a bit confusing. I always need to refresh my memory on this topic since I don't work with it daily.
At the core of asymmetric cryptography is the notion that you have a public and a private key. You can convert a message into a cipher using one of them, but you can only ever convert the cipher back to the message using the other one.
So if Alice wants to encrypt a message to Bob, she applies Bob's public key, and not even Alice herself would be able to decrypt that message, because she needs to apply Bob's private key, that only Bob has. Applying the public key twice will not work.
It's like a lock that has two keys, one for locking and a second one for unlocking.
If Alice wants to sign a message, it works the other way around. She uses her private key, that only she has. Bob, or anyone with access to her public key will be able to verify Alice's identity, provided he can safely verify that the public key is in fact Alice's. If Eve could make Bob believe that the public key belongs to Alice, when in fact it's Eve's key and she signed the message with her private key, then that would be a successful attack.
As with the encryption example, signing with a public key and checking with the same public key will not work, it has to be one for signing and the other for checking. That's why it is called asymmetric.
The attack you describes fails because Eve tries to sign a message using Bob's public key. If Alice were to verify this message using Bob's public key a second time, the verification would fail.
Code Review
After you gave your code example and a link to your sources, I was able to look a bit further into it.
Before we get started: If everything is done correctly, then applying the public key twice will not get you the same result as first applying the private key and then applying the public key. This is the reason why your suggested attack will fail. Let's get into why the attack seems to work in your code:
Problem 1: You have a bug in your code when you transferred it from the example. This line
hash = message**65537 % 2**8 # incorrect
should use multiplication instead of the power function
hash = message*65537 % 2**8 # correct
This bug was on you, but from here on out nothing is your fault, since the source you linked is faulty itself. So let's go ahead and fix your code bit by bit.
I will switch to the regular case of signing with a private key and checking with a public key to make sure the algorithm works and then we'll run your attack again.
Problem 2: Alice_confirmation
is not computed correctly. The idea is that Bob computes the hash, then encrypts the hash to get the signature. Now Alice decrypts the signature and compares it to the hash, which she also computes. This last step was switched around in the example.
So this
Alice_confirmation = Alice_hash**publickey[1] % publickey[0]
if Alice_confirmation == sig:
print("The message was sent by Bob")
should actually be switched around to look like this:
Alice_confirmation = sig**publickey[1] % publickey[0]
if Alice_confirmation == Alice_hash:
print("The message was sent by Bob")
This is a crucial difference and the reason why your attack seemed to work. If I'm not mistaken, the side effect is that a correctly signed message would fail the test (This did not happen in your original code due to problem 1, though).
Problem 3: I can't reconstruct how you got your private and public keys, so I will use the ones provided by the example website. The problem here is that as a rule, the hash must be smaller than n. In your case, the hash can grow to be 255 (due to the % 2**8
) which is greater than n = 91. This is even worse on the website since they use % 2**32
in their hash function which creates even greater numbers.
So let's instead take the values provided by the Wikipedia page at https://en.wikipedia.org/wiki/RSA_(cryptosystem). You are encouraged to try and roll your own, just make sure they are large enough for your hash function range.
public key: n = 3233, e = 17
private key: n = 3233, d = 413
Here your hashing function works, since a applying % 2**8
guarantees the result to be smaller than 256 and therefore smaller than n = 3233. In general it is probably a good idea to use a stronger hash, like the one provided in your example link (message * 2654435761 mod 2**32
), but then of course you would have to choose your n adequately, so that it exceeds 2**32
).
So, applying the fixes and cleaning up your code a little would give us this:
private_key = (3233, 17) # Bob's private key
public_key = (3233, 413) # Bob's public key
bob_message = 122
bob_hash = bob_message * 65537 % 2**8
bob_sig = bob_hash**private_key[1] % private_key[0]
# Alice receives Bob's message and his signature
alice_hash = bob_message * 65537 % 2**8 # same as bob_hash
alice_confirmation_hash = bob_sig**public_key[1] % public_key[0]
if alice_hash == alice_confirmation_hash:
print("The message was sent by Bob")
else:
print("The message was not sent by Bob")
print()
print(f"message: {bob_message}, signature: {bob_sig}")
print(f"hash: {alice_hash}, confirmation: {alice_confirmation_hash}")
If we run this code we get the following output:
The message was sent by Bob
message: 122, signature: 1830
hash: 122, confirmation: 122
In case you're wondering why the message is the same as the hash, this is a result of your message being smaller than 2**8
. A message greater than that will provide a hash that is different from the message.
Now let's run the attack you proposed: Eve uses Bob's public key when computing the signature. This results in Alice using the public key a second time when she tries to verify the signature, with the following result:
The message was not sent by Bob
message: 122, signature: 1159
hash: 122, confirmation: 1891
So here the hash and the confirmation hash do not match. Eve's attack has failed.