cryptographyelixirrsa

rsa signature in elixir mismatch with python code output


I have a python code for generating rsa signatures and I am trying to write this code in elixir. python code:

import sys
import base64
import datetime
import json
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA512

def generate_signature(key_id, private_key_content):
    private_key = RSA.import_key(base64.b64decode(private_key_content))

    timestamp = "2024-12-19T08:48:29.528457+00:00"
    message_to_sign = key_id + timestamp
    print("Message to sign:", message_to_sign)

    hash_obj = SHA512.new(message_to_sign.encode())
    signer = pkcs1_15.new(private_key)
    signature_bytes = signer.sign(hash_obj)
    signature_value = base64.b64encode(signature_bytes).decode()

    return json.dumps({
        "keyId": key_id,
        "timestamp": timestamp,
        "signature": signature_value
    }, indent=2)

def main():
    if len(sys.argv) < 3:
        print("required keyId and privateKey")
    else:
        key_id = sys.argv[1]
        private_key_content = sys.argv[2]
        print(generate_signature(key_id, private_key_content))

if __name__ == "__main__":
    main()

my elixir code:

defmodule RsaExample do
  require Logger

  defp api_key() do
    Application.get_env(:rsa_example, :api_key)
  end

  defp key_id() do
    Application.get_env(:rsa_example, :key_id)
  end

  def get_signature do
    dt = "2024-12-19T08:48:29.528457+00:00"

    message =
      :crypto.hash(:sha512, key_id() <> dt)
      |> Base.encode16()
      |> String.downcase()

    api_key = Base.decode64!(api_key())
    <<_::binary-size(26), der::binary>> = api_key

    key = :public_key.der_decode(:RSAPrivateKey, der)
    signature = :public_key.sign(message, :sha, key) |> Base.encode64()

    {:ok,
      %{
        keyId: key_id(),
        timestamp: dt,
        signature: signature
      }}
  end
end

I tested with these params:

private key:

MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4CuxG1G1KiXFWpLXAGWI0ATKRP+Ixto9ElVqpFsPRB14Cu0TaRA6rEMW2C8LgafUYAJdrfzypB1tlfPpRH1fpQoFNkbYnec5/DJGMrh4fWkSZEflrOcSnY0CEHVG2fO0AMT4toVmgj6svfBSMf6S2sm2rjhQjMSqj87wOG3TNaIvQSaAz7UXa+hzaI7LQ0a41iUUDI6JLYH1EbvjZQQhwjgWlqy5LSeosBXtpPyfLaQDfUaV8eAB5mCt6uV5NlCGKMpvT+h9ZvT8gRn9P9PQeCScFyNdR32LVuU7WNkiu55XI5/ZXRgV6PNN3tOEHPLxn1Wl5cZ4kbQbciAF+eo0JAgMBAAECggEAC/BtdoTWWDUFXL0Pq1gaNkxzltehmV8B+U2FFZ4L8vX648t5Qn2SxAcxBEfMCxnkk/uJ1yfobAC1raZHaNbTMacmUz1mJzZwLm7GzG0JODh3ZcS0PQAA4Wj/aPKr994v08jeA0DJ2zAmx0xR2vF24oE9uxja9pN8dCleJD4kvCR1aVMJBlUow09PnZo6fsi9wAdIjs/BHVQT0mUl7Nx+fFqhLbWgJB3+EdB2sudiQ6r4P622zNR0BAeYuoS93LTFTER/6QYRYfQ4+aVYCacJs9hI0L+2SQmEteF+TS4P2PQxvryvmcYYGRnfQLtmQmdLU54eHkaY88+gKGoN9cxbbwKBgQDP/4qExV7lNhgpHnlv9KwXSRLk3nHvShqWKBm1ofRxGvF8bhp2s/6AG88AiGOUzMMJiS6sFasx6upznzWRUU6wpQYPP8SIftYPCv2f0dm4lIxi+o9KJHbt2zXYkoEwhgaWwo2h/InzWksVV4nRh1nm9UfUWbhGPg0f/DCjcjFgAwKBgQDihBjlN5HVhXn/PTmYRdad5Gv7wsRuJS3eSn0qSW2wHyQTav/4ulx5R21Vjbos5hEfP9wSzypy8VUgtpr9hWaWrV4noPInuo/STsUVYmdC81B3/dNWCVJQOVWogK6uD8Rh7jYPcR9fGMAiliYAygGekVNmy66r9talVLjkPmzPAwKBgQDAMWNATdPivjpS/Gq/rXi6x3Xi7zyeHH42k3U2JSFmxbuv+1kOqEEZBRkgZ+aEHzR1AkKTFi6rIGNlVQ69aUDp7oKL4qNqcLDBE6nJXLHuYaza5KU0oD8Yh/7YUH95Y86AzeLrdBvQNnW8kbeyWXwT8j9eE0038qxUCsaysJi5GwKBgQC8VYzFeHCekb4fg+5RNy/8U6Gc0BG2at86RcDP2gGDQEEkjACL85dTlxnG2nIDRr7RtqzqTqlCrHlVG76+L55ehYMVe/IbKjjOaYPgBdNJjThIrBh9Hn78CM+5rFSQkLd9nSwBptKopNGLcD/kxBCYoMgxXgS7Ih7RlCGuVGDvtQKBgQDFXhexzngPKboXvKl9rz19DSZ98IIbNTwX7Svc5rfvKGHN0XMGpc93SXA4WBY8ZlX6M/NNRk2Gd9ePf64dhn2uViMMAGwvsadiCV6Z2BqpQnDGKq2VpAbuFqtnCDPIfgPKY4fbHFPjcO3n/+wFqTL6grbIP7yM76Fv0oqt+/t2Yw==

key id: 1151022712

python code output:

Message to sign: 11510227122024-12-19T08:48:29.528457+00:00
b'11510227122024-12-19T08:48:29.528457+00:00'
{
  "keyId": "1151022712",
  "timestamp": "2024-12-19T08:48:29.528457+00:00",
  "signature": "gumnKBrIiRn4nlxOYe+2JASgngiFh71jpX8u/wfqH0bOw2S3B+X3sk+2pCYc9w3bmjZL2uMwAcb8dnN8UEZFKs03xo4s4RFjaqQpfwCKv1IFidNhilIiF7oyx2PkGBxVVAMtH0srO6wxmYKGFjOvCNSS5/edM7AwuXb4ZRe+6wQrmNMV/nwiKQ3Onqc5SnyaFjxINLo2/eiG4Ks0ngkmdT6PhPYhTW8Zy0EAZszo0ZK3W6z+uEMfQxw1veFa0TqcdJSQws9YZZIqBdw8mbZmY1CUZD7SwPT3UQZRw6eJezBtq/wcNO/afaW2MB9ejpkxNYr/+cFYglBTbNaZLzOK9g=="
}

elixir code output:

{:ok,
 %{
   timestamp: "2024-12-19T08:48:29.528457+00:00",
   signature: "lXl157EEip2OVA0UpR72oAiEjER0eDRXr5kr4fujwSZLj0/KPfkO5+1tRKY2rJVGIQ6/FAlMDtQqTijltLBvZXGqf8YWhU0zYiAzHmPZZ5WDg4Z08vwjhMDnhtsxk7MtTbv1ZzLPolCg0LOMlZqmVDaCADMl8QB9c8CHXKOogBGUP50+ML25OCvV6Ti9ni8QFfLR91fBeBYTFf0dZNqxOM/T3wX6qUAAcDnl0624Cp/UJOwDiHOivSYZJ+qsvR4ExoSYAK2RbZZOHuOIA2GrPgAOxsDjvHlBkMAa+e2lPdlAC6h7Thqla7sojeySiPWbkVf4KADBNOGisEVrnI0FaA==",
   keyId: "1151022712"
 }}

I tried different libs for working with rsa in elixir, but my elixir code output always mismatch with python code output. I am new to cryptography, maybe I am making some mistake? How to make signatures match?


Solution

  • Both codes generate different signatures, as the Elixir code does something different to the Python code:
    In the Elixir code, first the SHA-512 hash of the message is generated and the result is hex encoded (with lowercase letters). This result is then hashed with SHA-1 and signed.
    In the Python code, on the other hand, the message is "only" hashed and signed with SHA-512.

    So that the Elixir code does the same as the Python code, you should omit the first hashing with SHA-512. When signing, you should use the message directly and SHA-512 as digest:

    defmodule RsaExample do
      require Logger
    
      defp api_key() do
        #Application.get_env(:rsa_example, :api_key)
        "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4CuxG1G1KiXFWpLXAGWI0ATKRP+Ixto9ElVqpFsPRB14Cu0TaRA6rEMW2C8LgafUYAJdrfzypB1tlfPpRH1fpQoFNkbYnec5/DJGMrh4fWkSZEflrOcSnY0CEHVG2fO0AMT4toVmgj6svfBSMf6S2sm2rjhQjMSqj87wOG3TNaIvQSaAz7UXa+hzaI7LQ0a41iUUDI6JLYH1EbvjZQQhwjgWlqy5LSeosBXtpPyfLaQDfUaV8eAB5mCt6uV5NlCGKMpvT+h9ZvT8gRn9P9PQeCScFyNdR32LVuU7WNkiu55XI5/ZXRgV6PNN3tOEHPLxn1Wl5cZ4kbQbciAF+eo0JAgMBAAECggEAC/BtdoTWWDUFXL0Pq1gaNkxzltehmV8B+U2FFZ4L8vX648t5Qn2SxAcxBEfMCxnkk/uJ1yfobAC1raZHaNbTMacmUz1mJzZwLm7GzG0JODh3ZcS0PQAA4Wj/aPKr994v08jeA0DJ2zAmx0xR2vF24oE9uxja9pN8dCleJD4kvCR1aVMJBlUow09PnZo6fsi9wAdIjs/BHVQT0mUl7Nx+fFqhLbWgJB3+EdB2sudiQ6r4P622zNR0BAeYuoS93LTFTER/6QYRYfQ4+aVYCacJs9hI0L+2SQmEteF+TS4P2PQxvryvmcYYGRnfQLtmQmdLU54eHkaY88+gKGoN9cxbbwKBgQDP/4qExV7lNhgpHnlv9KwXSRLk3nHvShqWKBm1ofRxGvF8bhp2s/6AG88AiGOUzMMJiS6sFasx6upznzWRUU6wpQYPP8SIftYPCv2f0dm4lIxi+o9KJHbt2zXYkoEwhgaWwo2h/InzWksVV4nRh1nm9UfUWbhGPg0f/DCjcjFgAwKBgQDihBjlN5HVhXn/PTmYRdad5Gv7wsRuJS3eSn0qSW2wHyQTav/4ulx5R21Vjbos5hEfP9wSzypy8VUgtpr9hWaWrV4noPInuo/STsUVYmdC81B3/dNWCVJQOVWogK6uD8Rh7jYPcR9fGMAiliYAygGekVNmy66r9talVLjkPmzPAwKBgQDAMWNATdPivjpS/Gq/rXi6x3Xi7zyeHH42k3U2JSFmxbuv+1kOqEEZBRkgZ+aEHzR1AkKTFi6rIGNlVQ69aUDp7oKL4qNqcLDBE6nJXLHuYaza5KU0oD8Yh/7YUH95Y86AzeLrdBvQNnW8kbeyWXwT8j9eE0038qxUCsaysJi5GwKBgQC8VYzFeHCekb4fg+5RNy/8U6Gc0BG2at86RcDP2gGDQEEkjACL85dTlxnG2nIDRr7RtqzqTqlCrHlVG76+L55ehYMVe/IbKjjOaYPgBdNJjThIrBh9Hn78CM+5rFSQkLd9nSwBptKopNGLcD/kxBCYoMgxXgS7Ih7RlCGuVGDvtQKBgQDFXhexzngPKboXvKl9rz19DSZ98IIbNTwX7Svc5rfvKGHN0XMGpc93SXA4WBY8ZlX6M/NNRk2Gd9ePf64dhn2uViMMAGwvsadiCV6Z2BqpQnDGKq2VpAbuFqtnCDPIfgPKY4fbHFPjcO3n/+wFqTL6grbIP7yM76Fv0oqt+/t2Yw=="
      end
    
      defp key_id() do
        #Application.get_env(:rsa_example, :key_id)
        "1151022712"
      end
    
      def get_signature do
        dt = "2024-12-19T08:48:29.528457+00:00"
    
        #message =
        #  :crypto.hash(:sha512, key_id() <> dt)
        #  |> Base.encode16()
        #  |> String.downcase()
        message = key_id() <> dt # fix1: apply the message directly
    
        api_key = Base.decode64!(api_key())
        <<_::binary-size(26), der::binary>> = api_key
    
        key = :public_key.der_decode(:RSAPrivateKey, der)
        signature = :public_key.sign(message, :sha512, key) |> Base.encode64() # fix2: apply SHA-512
    
        {:ok,
          %{
            keyId: key_id(),
            timestamp: dt,
            signature: signature
          }}
      end
    end
    
    IO.inspect RsaExample.get_signature() #... signature: "gumn...OK9g==",...