SAML Token: Why use multiple Hash/Digest Algorithms: sha256 and sha1? - digital-signature

I'm reading this IBM Developer Works SAML article, where you can see two different signature/digest algorithms below: sha1 and sha256. I'm curious why both are listed and what is being signed by which method. That article says, "the [IdP] applies a hashing algorithm to the content of the Assertion element." That seems to be sha256, but then why mention DigestMethod sha1? If both methods are hashing the same content, how would the service provider (SP) know to verify the signature using sha256 when sha1 is the DigestMethod?
Is this StackOverflow post the correct explanation?
...the DigestMethod Algorithm elements refer to their parent Reference element. You can have different DigestMethod Algorithms for your multiple Reference elements, and again a different SignatureMethod Algorithm to hash and sign all the References.
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
ID="Assertion1487321564650" IssueInstant="2017-02-17T08:52:44.650Z"
Version="2.0">
<saml2:Issuer>samlsso.sample.net</saml2:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod
Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<ds:Reference URI="#Assertion1487321564650">
<ds:Transforms>
<ds:Transform
Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<ds:DigestValue>nKYxEAMG1LY4H+LqR22KJ/vqyb8=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
m5V44OFKU1PMdibileobvVVA8NVZMKRmKAauOin2f+Kr1WQ [...] Z/5JcU/qw==
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDRzCCAi+gAwIBAgIEKa/crTANBgkq

The XML element SignedInfo is what is being signed using the private key and SHA-256. Not shown is the actual signature algorithm but if you look up rsa-sha256 then you'll find that it uses the PKCS#1 v1.5 signature scheme rather than the newer PSS.
This SignedInfo contains the reference to the signed data, the transformations (canonicalization) and the signature value of the signed data. This is not part of the data within the regular signature generation; that can only contain a hash value. So the meta-information is signed as well. There can be multiple references and different kind of transformations in SignedInfo.
Of course the references themselves must be signed together with the hash over the data; otherwise you could for instance switch the data around when two data blocks have been hashed.
You should however still verify that the hash is over the correct data within your application. Otherwise you will verify some data in the XML, while processing entirely different data. This should of course be performed by the SAML implementation.
More information can of course be retrieved from the XML-digsig standard.
Yes, the linked-to StackOverflow post is correct.
I'll not lament the fact that SHA-1 is used calculate the hash over the data. SHA-1 has been broken and should not be used anymore; and certainly not for data hashing or signature generation.

Related

Bad padding exception after encrypting a message with RSA SHA1 OAEP MGF1

I need to encrypt some message before sending it as request body in an API. I have to do this in Python and the library I used is Pycryptodome. From hereon, when I use the term API, I am referring to the API endpoint provided by the vendor and not the Pycryptodome API. The receiver(API vendor) shared the certificate (RSA 2048 bits) which I need to use to encrypt this message. This certificate is in binary format (DER) and has .cer as the extension. I need to import this using Pycryptodome and from the docs (https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html), I find that the import_key method accepts X.509 certificates in either binary or PEM encoding. The API documentation says I need to use RSA/NONE/OAEPWithSHA1AndMGF1Padding for encryption. This is my code.
def encrypt_message(message):
with open('/home/krishna/work/.certificates/random_certificate.cer', 'rb') as _file:
key_contents = _file.read()
recipient_key = RSA.import_key(key_contents)
cipher_rsa = PKCS1_OAEP.new(recipient_key)
return base64.b64encode(cipher_rsa.encrypt(message.encode())).decode()
I read the PKCS1_OAEP.py file, the default mask generation function is MGF1, default hash algorithm is SHA1. Hence, I did not specify these parameters in the new function in the above code. Also, I need to send this encrypted message as a base64 string (UTF-8). After reading the API documentation and the Pycryptodome documentation, I felt this code is sufficient for encryption. Also, this code is somewhat standard and is almost the same as the one in the Pycryptodome documentation. However, I get a decryption error and the vendor tells me this is due to "BAD PADDING EXCEPTION". This should not happen since I used OAEP for padding and the vendor confirmed they are using OAEP for padding as well. This is very hard for me to debug as I do not possess the vendor's private key.
So I tried generating my own pair of private and public keys (RSA-2048) and checked if I am able to decrypt my message. And duh, I am able to decrypt my message. Here is the code for encryption and decryption using my own keys.
def encrypt_message(message):
with open('my_pub.pem', 'rb') as _file:
pub = _file.read()
recipient_key = RSA.importKey(pub)
cipher_rsa = PKCS1_OAEP.new(recipient_key)
return base64.b64encode(cipher_rsa.encrypt(message.encode())).decode()
def decrypt_message(gibberish):
with open('my_priv.pem', 'rb') as _file:
priv = _file.read()
pvt_key = RSA.importKey(priv)
cipher_rsa = PKCS1_OAEP.new(pvt_key)
return cipher_rsa.decrypt(base64.b64decode(gibberish.encode())).decode()
So where I am going wrong? They key difference between this test and the actual API call is that my keys are generated in PEM format and have .pem as the extension. In the case of the API call, I have to use a .cer certificate in DER format. But I am assuming Pycryptodome is handling this for me (from the docs).
I have a few more questions regarding this.
What is NONE in RSA/NONE/OAEPWithSHA1AndMGF1Padding?
What is the difference between import_key() and importKey() in Pycryptodome? There are 2 methods and it does not look like importKey() has some documentation to it.
Can padding exceptions occur while using OAEP? From what I read from the internet, OAEP is the best method out there, not only in terms of adding randomness during encryption, but I also read that padding exceptions are very rare when this is used.
I really need to know what is going wrong here as I am dead clueless. Any suggestions or help will be appreciated.

How do I validate the Hmac using NodeJS?

I can successfully create an Hmac via NodeJS using the following code:
(slightly altered example from : https://nodejs.org/api/crypto.html#cryptocreatehmacalgorithm-key-options)
Crypto.createHmac('sha256', Crypto.randomBytes(16))
.update('I love cupcakes')
.digest('hex');
That results in a value like the following (hex-based string Hmac signature):
fb2937ca821264812d511d68ae06a643915931375633173ba64af9425f2ffd53
How do I use that signature to verify that the data was not altered? (using NodeJS, of course).
My Assumption
I'm assuming there is a method call where you supply the data and the signature and you get a boolean that tells you if the data was altered or not -- or something similar.
Another Solution?
Oh, wait, as I was writing that I started thinking...
Do I need to store the original random bytes I generated (Crypto.randomBytes(16)) and pass them to the receiver so they can just generate the HMac again and verify that the result is the same (fb2937ca821264812d511d68ae06a643915931375633173ba64af9425f2ffd53)?
If that is true that would be odd, because the parameter for Crypto.randomBytes(16) is named secret (in the official example)*. Seems like that needs to be kept secret??
Please let me know if there is a way to verify the signature on the receiving side & how I do that.
Official Documentation : A Bit Confusing
Here's the function as it is defined in the official docs:
crypto.createHmac(algorithm, key[, options])
In the function definition, you can see the second param is named key.
However, in the example they refer to it as secret
const secret = 'abcdefg';
const hash = crypto.createHmac('sha256', secret)
.update('I love cupcakes')
.digest('hex');
console.log(hash);
Just posting the answer so if anyone in future sees this they will be able to have the definitive answer.
As the commentor (Topaco) pointed out, the simple answer is that:
The receiver who want wants to validate the Hmac simply needs to use the same key value & data and apply it to the method and retrieve the hash value.
const secret = 'abcdefg';
const hash = crypto.createHmac('sha256', secret)
.update('I love cupcakes')
.digest('hex');
console.log(hash);
The original Hmac-creating party must provide three things for the verifying party:
data : (could be encrypted data from AES256, for example)
key : original key passed into the createHmac() method -- note: this item is called secret in the sample code by NodeJS (above).
hash :the (clearText) hash which the original creator generated when calling the createHmac() method.
With those three things the verifying party can now call the createHmac() method and determine if the hash they get matches the hash that the original hmac-creating party generated.
Doing this validates that the Data which was sent has not been corrupted or altered.
Additional Note On Key (secret)
I've come back after thinking about the Hmac a bit more.
It is required that both parties know the key (aka secret) but it does not mean that it should be exposed to others.
This must be kept secret (as the code implies) because if a nefarious type knew the value and could alter it, then they could also alter the data and generate a new key (secret) and pass it along as if the original creator sent it along (MITM - man in the middle attack).
So, the point here is that yes, both parties have to know the key (secret) value, but it should not be shared where it might be discovered by nefarious types.
Instead, it will have to be agreed upon or based upon a secret password, etc.

Problems decrypting data in node.js (RSA PKCS 1.5)

I am having great difficulty implementing some encryption for a node.js project. I am using node-rsa which in turn uses the built in node crypto module.
My code appears to encrypt the data just fine and I can decrypt it. Unfortunately, the system I am sending the data to cannot. Having hit a bit of a wall I've tried decryption some data generated the other end. That also fails.
What is unusual in this case is that the data has to be encrypted using the PRIVATE key and decrypted using the PUBLIC key. As stupid as this is, I had no say in the decision to use this mechanism and it is already in wide use in a UK pubic transport ticketing system. So there is no possibility of doing anything different.
The specification [only] says:
The payload data (Appendix A) is encrypted using the TIS private key,
using 1024Bit RSA PKCS#1v1.5. (This is a freely available public
standard). This protects a payload of up to 116 bytes, or 928Bits,
creating a 128 byte or 1024Bit encrypted output. The RSA encryption
algorithm adds a header of length 12 bytes to the payload before
encryption to improve security. For decryption purposes, this header
should be removed to get the correct payload.
The format of this header is: 0x00 [10 non-zero bytes which are
padding] 0x00 [content]. This is often done automatically by
encryption/decryption software.
Here is an example public key:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDriuvWAKiTKFgmjHjHYjWK4IZ6
W1UOdrntxFAGT1szZlbKJfWGeBOPSk6MnW6AeX54zEta74Dnoj4MBT+MlekVQ2zp
G3qIZpp4DR+kF6eVRlguUg9l/Fgr9ZrFgkumCKIx/ccc6rvZcNvPnOVdPCBVoMRr
3X59WlLmH4d+RAivAQIDAQAB
-----END PUBLIC KEY-----
I've derived this from the Modules / Exponent listed below... I think it is correct...?
Which is [supposedly] valid to decode this encrypted data:
1f876b22ea8af4341c4198347aabf1b1ba40649ad6918bbe26ebfcf4633786a2669ee499d796cf438a76ff0d4f6efc43f605994330e6fb88c8117eea6aad1a12c2525940f7b9946b3f6cbd674e2740070400ae6f2cf1ed6d36f610822a23d5a8a4717ecf671bf0530200f75a269951020be9f69a992d423f55eb6fefe6caa449
I get the following error:
decryptData error Error: Error during decryption (probably incorrect key). Original error: Error: error:0407006A:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01
I hate to say it, but I am now completely stumped. If anyone is able to decrypt that data using the supplied key and can explain how I would be very grateful.
Just to save the usual response to this sort of question that I see whenever anyone else has asked a similar question in the past. Please do not comment if all you want to say is the process is wrong. That is not helpful in any way. Just to confirm, whilst encryption with the private key is unusual, it is not impossible....It is supported by the RSA specification.
There are several ticket retailers who are encrypting their data this way and several scanning equipment manufacturers who are producing equipment that decrypts in this way. It's out there, it's working.
The only issue seems to be that there is some small mismatch between the encrypted data I am seeing from node-rsa (node.js crypto library) and the example data I have been given.
This is the original format the keys were given to me.....
Appendix D - Example Security Keys for Tickets
All example tickets are encrypted using the following fixed 1024Bit RSA key
(Never use these keys in production, you must generate your own key pair for this purpose as this private key cannot be considered secure and is for testing only)
Modulus = 16540368155253773454125654483794538435594869143015457768150677916704644349481001621163 93533906833356026635527060528398017212524170789714748325265707476885395169247950736258 76338137535298120673942399072260833758038152595105311260002268650951968659366409471762 707986435753824060971148933772149012757770173722369
= HEX [00eb8aeb d600a893 2858268c 78c76235 8ae0867a 5b550e76 b9edc450 064f5b33 6656ca25 f5867813 8f4a4e8c 9d6e8079 7e78cc4b 5aef80e7 a23e0c05 3f8c95e9 15436ce9 1b7a8866 9a780d1f a417a795 46582e52 0f65fc58 2bf59ac5 824ba608 a231fdc7 1ceabbd9 70dbcf9c e55d3c20 55a0c46b dd7e7d5a 52e61f87 7e4408af 01]
Public Exponent = 65537
= HEX [010001]
Private Exponent = 23262061627321059848127951748956201956280865601289878122136319690902346303487555417896 45421978315004119428666694674801184773156427997741861134011936129888508204465790901818 10860126020371138225614906842567831455763314175530505369901364910691824152015094858350 72189773434923487177372245370361799421231555208773
= HEX [21205394 b0590501 3a8c895a ff2797c2 255ba45f adf1afce ec5a9caa 96848c11 0b89b896 f44774f0 c5119103 1f246071 e209515b c3ad4c66 6bf582d3 72312f2b 7250fe61 f6abed7f e219c08d c3985ae1 3f6b6db2 0e3c040b df7a817d 14a5a6f1 20d94047 08512132 aca00baa 29805440 4ad5dec2 1bd544bb 8938c74b 2904e645]
Just to confirm... I was given incorrect data.

Verify SHA1withECDSA signature on javacard

i have self-signed certificate signed by SHA1withECDSA algorithm using BouncyCastle.
Under BC i can verify it easily, but when i`m doing it on JavaCard it send me false everytime(Curve secp192r1 from NIST). Certificate hold sign in plain (non X9.62 mean just r+s without any TAGs).
There is my code to verify it (with values putted as constant - for tests of course).
byte[] certdata = {...}
Signature signature = Signature.getInstance(Signature.ALG_ECDSA_SHA, false);
ECPublicKey ecpk = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, KeyBuilder.LENGTH_EC_FP_192, true);
ecpk.setA(new byte[]{...}, (short)0, (short)0x0018);
ecpk.setB(new byte[]{...}, (short)0, (short)0x0018);
ecpk.setG(new byte[]{...}, (short)0, (short)0x0031);
//Point format: uncompressed tag(0x04), x, y
ecpk.setK((short)0x0001);
ecpk.setR(new byte[]{}, (short)0, (short)0x0018);
ecpk.setW(new byte[]{}, (short)0, (short)0x31);
ecpk.setFieldFP(new byte[]{}, (short)0, (short)0x0018);
signature.init(ecpk, Signature.MODE_VERIFY);
boolean result = signature.verify(certdata, (short)0, (short)certdata.length, signtab, (short)0, (short)signtab.length);
if(result) ISOException.throwIt((short)0x0001);
else ISOException.throwIt((short)0x0002);
}
'...' instead of bytes for clear view (192bits curve can do big mess).
Certificate with TAGS explanation on pastebin:
http://pastebin.com/gvqYzm2g
Thanks for any help
sevar
edit:
New tests:
All tests re on same data (PublicKey, PrivateKey, Message to be signed)
sign is randomized so i ll use 2 sign (signT - sign generated by Terminal (BC), signC - sign generated by Chip)
signT cant be verified on CHIP but can be verified on Terminal.
signC is verified on CHIP & Terminal
so I checked cross between API
Cross Relation directed to BC works well
Cross Relation directed to CHIP isn't work
pair of key generated well because when i put PrivateKey and PublicKey generated by BC to CHIP, then signature generated on the CHIP can be verified by CHIP.
KeyPair generated well
I have no idea what i should check now. Problem probably can be with filling array in ECDSA step e = SHA1(Message). Whats happen with array after hash(hash is shorter than curve and card needs to declare size of array before copy)
Signing and verification of ECDSAwithSHA-1 with Prime192v1 from Bouncy Castle to JavaCard works fine for me.
Your problem is probably format of the signature itself.
Signature is a DER encoded structure, it is a sequence (tag 0x30) of two integers (tag 0x02). In JavaCard, 56 bytes are always expected: two coordinates of length 25 plus 6 bytes of DER headers. JavaCard always expects leading zeros in each coordinate. However, BC often produces signatures without leading zeros in coordinates, so the signature can be shorter than 56 bytes and that is why JavaCard is confused.
The opposite direction is always OK, because BC can handle leading zeros, although it does not add them when creating signature.
What you should do: wrap the BC signing mechanism with your own code and ALWAYS add leading zeros to coordinates in your BC signature. If you do so, you will be able to verify the signature both in BC and JavaCard.
I would like to post my code, but unfortunately it is a part of a commercial security project...
I had a problem with verifinge a ECDSA singature (generated on JavaCard 192r1) using BouncyCastle and i found a solution. I hope that it will be useful
Verify javacard signature ALG_ECDSA_SHA on bouncy castle

From CRMF request into CertificateRequest (PKCS#10) Signature

I did a example code to understand how to get a CRMF (mozilla certificate request) to convert it into a CSR more similar to PKCS#10
I got the Base 64 CRMFRequest as a ASN1InputStream type.
I convert it into a CertReqMsg type (Bouncycastle)
when I debug, I realize the CertReqMsg have the public key, another data like Subject (CN, O, OU, etc)and other, but more important, it has a signature and an AlgoritmIdentifier.
but the object doesn't have getters
How I extract the signature as a DERBitString...? I need it to use as parameter to the CertificationRequest object (which returns the CSR as I want it)
by the way, the CertificationRequest need a CertificationRequestInfo object as parameter. and inside it (CertificationRequestInfo ), it receives Attributes as parameter . I supose to this attributes are of the kind of:
distributionPoint, unotice, policyOID, subjectAlternativeNameDN
I know that it start with a
ASN1Set attributes = null;
attributes = new DERSet();
But I don't know how to fill this paramethers to
CertificationRequestInfo info = new CertificationRequestInfo(subject, infoPublicKey, attributes);
Sorry if some question seems obvious... but I can't find the solve..
Thanks in advance
You won't be able to convert the CRMF format into a PKCS#10 CSR.
The CSR is structured like this and signed by the subject's private key:
CertificationRequest ::= SEQUENCE {
certificationRequestInfo CertificationRequestInfo,
signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
signature BIT STRING
}
(Essentially, it's very similar to a self-signed X.509 certificate, without issuer and validity dates.)
Since when you get the CRMF request, you won't have the subject's private key, you won't be able to make this signature.
If you're writing some sort of CA software, you don't really need this. Processing a CRMF request and a CSR request is more or less equivalent. A CA shouldn't really do what the CSR wants blindly anyway, so it would have to vet the attributes it associates with the public key and identity some other way anyway.

Resources