I got a rsa primary key from an android app, but I don't know how to use it in python.
in java code:
public class RSAHelper
{
public static String decrypt(String paramString)
throws Exception
{
return new String(RSAUtils.decryptByPrivateKey(Base64Utils.decode(paramString), "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIrrUGxh+yvNNI1c9hUg1rH+EtipI0nPk3zRm2Cj4mLDWLJ6DaTzdJTXTF3BYZaancWeG3QtBL+fITUi72InwBP7zaNG8uv/guwuhWT6V/YO7AaTrOFeTkg9NXuaFbn3hWVtZxQm2tIlaVa8snoNj3VGnPqIjXmGcxk4axuYd7sTAgMBAAECgYA43YhnRVh2nqJzd2k4Tt/zrmhyjhHm5fSetIKg9ZT3DrXhITsymYHQZ61X95AGATayLT1Zug/mjLIgOTO6f0ENkRQtjVCmKd8Yf/BeDEc5kRLUYDfSqoEydHK0+rCw5tJMgrAnQc5lHc+FVdGe2bOxKTEtZoss9VQ2jYuQ+Z5fUQJBANnvDOcI2OYSksX3PpHzO9F272xkmqYBRGkMc/a5RuOv1CY6FqMIkkloTf6nVl9y6XYV8gnHfbbI/wj4Q4UnPYsCQQCjLxyRYaOeEb/qOzSmFXytgMuCM9sr4eY9jpjzDgNWhpbtaVaf1QvSTXqN0zaUu4Se2tmWGX7zXw9p/dFf8DmZAkEAzl1o0FU2XhZ0WXVYEIhMunpvGSrirhNBHmAmZxjmoa/bqh8TVGpHa6+TO3JlfZioraL2QIBg8Ha/2VSNS0bvJQJALfCLaFpGh6+TicuVLNSLvwStRkB3CUmVWesVIAfn5KoLP1cSbfi6VUA+qkK18PVBhr8x1lHjLXyriDlOgmXMsQJAW9vD/IoBs4QJF87xF7tZvu/b1KRVgLM1edqOgVwMNbIQHBAXghjVjrpuln5w6z1dJ2cEjRP98OxKC0hqEIwIuQ=="), Charset.defaultCharset());
}
public static String encrypt(String paramString)
throws Exception
{
return Base64Utils.encode(RSAUtils.encryptByPrivateKey(paramString.getBytes(), "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIrrUGxh+yvNNI1c9hUg1rH+EtipI0nPk3zRm2Cj4mLDWLJ6DaTzdJTXTF3BYZaancWeG3QtBL+fITUi72InwBP7zaNG8uv/guwuhWT6V/YO7AaTrOFeTkg9NXuaFbn3hWVtZxQm2tIlaVa8snoNj3VGnPqIjXmGcxk4axuYd7sTAgMBAAECgYA43YhnRVh2nqJzd2k4Tt/zrmhyjhHm5fSetIKg9ZT3DrXhITsymYHQZ61X95AGATayLT1Zug/mjLIgOTO6f0ENkRQtjVCmKd8Yf/BeDEc5kRLUYDfSqoEydHK0+rCw5tJMgrAnQc5lHc+FVdGe2bOxKTEtZoss9VQ2jYuQ+Z5fUQJBANnvDOcI2OYSksX3PpHzO9F272xkmqYBRGkMc/a5RuOv1CY6FqMIkkloTf6nVl9y6XYV8gnHfbbI/wj4Q4UnPYsCQQCjLxyRYaOeEb/qOzSmFXytgMuCM9sr4eY9jpjzDgNWhpbtaVaf1QvSTXqN0zaUu4Se2tmWGX7zXw9p/dFf8DmZAkEAzl1o0FU2XhZ0WXVYEIhMunpvGSrirhNBHmAmZxjmoa/bqh8TVGpHa6+TO3JlfZioraL2QIBg8Ha/2VSNS0bvJQJALfCLaFpGh6+TicuVLNSLvwStRkB3CUmVWesVIAfn5KoLP1cSbfi6VUA+qkK18PVBhr8x1lHjLXyriDlOgmXMsQJAW9vD/IoBs4QJF87xF7tZvu/b1KRVgLM1edqOgVwMNbIQHBAXghjVjrpuln5w6z1dJ2cEjRP98OxKC0hqEIwIuQ=="));
}
}
I try in python:
key = "MIICdgIBADANBgkqhkiG9w0BAQEFA..."
rsa.PrivateKey.load_pkcs1(key)
but I got:
ValueError: No PEM start marker "b'-----BEGIN RSA PRIVATE KEY-----'" found
how can I load this string primary key using python rsa module?
add BEGIN and END
In [109]: key
Out[109]: '-----BEGIN RSA PRIVATE KEY-----\nMIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIrrUGxh+yvNNI1c9hUg1rH+Etip I0nPk3zRm2Cj4mLDWLJ6DaTzdJTXTF3BYZaancWeG3QtBL+fITUi72InwBP7zaNG8uv/guwuhWT6V/YO7AaTrOFeTkg9NXuaFbn3hWVtZxQm2tIlaVa8snoN j3VGnPqIjXmGcxk4axuYd7sTAgMBAAECgYA43YhnRVh2nqJzd2k4Tt/zrmhyjhHm5fSetIKg9ZT3DrXhITsymYHQZ61X95AGATayLT1Zug/mjLIgOTO6f0EN kRQtjVCmKd8Yf/BeDEc5kRLUYDfSqoEydHK0+rCw5tJMgrAnQc5lHc+FVdGe2bOxKTEtZoss9VQ2jYuQ+Z5fUQJBANnvDOcI2OYSksX3PpHzO9F272xkmqYB RGkMc/a5RuOv1CY6FqMIkkloTf6nVl9y6XYV8gnHfbbI/wj4Q4UnPYsCQQCjLxyRYaOeEb/qOzSmFXytgMuCM9sr4eY9jpjzDgNWhpbtaVaf1QvSTXqN0zaU u4Se2tmWGX7zXw9p/dFf8DmZAkEAzl1o0FU2XhZ0WXVYEIhMunpvGSrirhNBHmAmZxjmoa/bqh8TVGpHa6+TO3JlfZioraL2QIBg8Ha/2VSNS0bvJQJALfCL aFpGh6+TicuVLNSLvwStRkB3CUmVWesVIAfn5KoLP1cSbfi6VUA+qkK18PVBhr8x1lHjLXyriDlOgmXMsQJAW9vD/IoBs4QJF87xF7tZvu/b1KRVgLM1edqO gVwMNbIQHBAXghjVjrpuln5w6z1dJ2cEjRP98OxKC0hqEIwIuQ==\n-----END RSA PRIVATE KEY-----'
In [110]: rsa.PrivateKey.load_pkcs1(key)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-110-0a0425032302> in <module>()
----> 1 rsa.PrivateKey.load_pkcs1(key)
c:\users\winside824\appdata\local\programs\python\python35-32\lib\site-packages\rsa\key.py in load_pkcs1(cls, keyfile, f ormat)
73
74 method = cls._assert_format_exists(format, methods)
---> 75 return method(keyfile)
76
77 #staticmethod
c:\users\winside824\appdata\local\programs\python\python35-32\lib\site-packages\rsa\key.py in _load_pkcs1_pem(cls, keyfi le)
509
510 der = rsa.pem.load_pem(keyfile, b('RSA PRIVATE KEY'))
--> 511 return cls._load_pkcs1_der(der)
512
513 def _save_pkcs1_pem(self):
c:\users\winside824\appdata\local\programs\python\python35-32\lib\site-packages\rsa\key.py in _load_pkcs1_der(cls, keyfi le)
457 raise ValueError('Unable to read this file, version %s != 0' % priv[0])
458
--> 459 as_ints = tuple(int(x) for x in priv[1:9])
460 return cls(*as_ints)
461
c:\users\winside824\appdata\local\programs\python\python35-32\lib\site-packages\rsa\key.py in <genexpr>(.0)
457 raise ValueError('Unable to read this file, version %s != 0' % priv[0])
458
--> 459 as_ints = tuple(int(x) for x in priv[1:9])
460 return cls(*as_ints)
461
TypeError: int() argument must be a string, a bytes-like object or a number, not 'Sequence'
solveļ¼
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from base64 import b64decode
key = "MIICdgIBADANBgkqhkiG9w0BAQEFA..."
msg = "plain text"
key = b64decode(key)
private_key = RSA.importKey(key)
cipher = PKCS1_v1_5.new(private_key)
cipher.encrypt(msg)
rsa.PrivateKey.load_pkcs1 uses the PEM format by default.
As such it requires the key to begin with '-----BEGIN RSA PRIVATE KEY-----\n and end with
'\n-----END RSA PRIVATE KEY-----'.
try
pem_prefix = '-----BEGIN RSA PRIVATE KEY-----\n'
pem_suffix = '\n-----END RSA PRIVATE KEY-----'
key = "MIICdgIBADANBgkqhkiG9w0BAQEFA..."
key = '{}{}{}'.format(pem_prefix, key, pem_suffix)
rsa.PrivateKey.load_pkcs1(key)
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
Go generates a signature using a DSA private key
Java verifies first step result using the DSA public key
Java should return true, but returns false
package main
import (
"crypto/dsa"
"crypto/rand"
"encoding/asn1"
"encoding/hex"
"fmt"
"golang.org/x/crypto/ssh"
"math/big"
)
func main() {
// a dsa private key
pemData := []byte("-----BEGIN DSA PRIVATE KEY-----\n" +
"MIIBvAIBAAKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR\n" +
"+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb\n" +
"+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdg\n" +
"UI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlX\n" +
"TAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCj\n" +
"rh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQB\n" +
"TDv+z0kqAoGBAIb9o0KPsjAdzjK571e1Mx7ZhEyJGrcxHiN2sW8IztEbqrKKiMxp\n" +
"NlTwm234uBdtzVHE3uDWZpfHPMIRmwBjCYDFRowWWVRdhdFXZlpCyp1gMWqJ11dh\n" +
"3FI3+O43DevRSyyuLRVCNQ1J3iVgwY5ndRpZU7n6y8DPH4/4EBT7KvnVAhR4Vwun\n" +
"Fhu/+4AGaVeMEa814I3dqg==\n" +
"-----END DSA PRIVATE KEY-----")
// parse dsa
p, _ := ssh.ParseRawPrivateKey(pemData)
pp := p.(*dsa.PrivateKey)
// orign data
hashed := []byte{1}
r, s, _ := dsa.Sign(rand.Reader, pp, hashed)
type dsaSignature struct {
R, S *big.Int
}
var ss dsaSignature
ss.S = s
ss.R = r
signatureBytes, _ := asn1.Marshal(ss)
// print sign
fmt.Println(hex.EncodeToString(signatureBytes))
}
Java reads the DSA public key and initialize a signer
Java verify first step sign result
returns false
#Test
public void ttt() throws InvalidKeySpecException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
// DSA public key
String pubKey = "-----BEGIN PUBLIC KEY-----\n" +
"MIIBuDCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9E\n" +
"AMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f\n" +
"6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv\n" +
"8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7LvKtc\n" +
"NrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwky\n" +
"jMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/h\n" +
"WuWfBpKLZl6Ae1UlZAFMO/7PSSoDgYUAAoGBAIb9o0KPsjAdzjK571e1Mx7ZhEyJ\n" +
"GrcxHiN2sW8IztEbqrKKiMxpNlTwm234uBdtzVHE3uDWZpfHPMIRmwBjCYDFRowW\n" +
"WVRdhdFXZlpCyp1gMWqJ11dh3FI3+O43DevRSyyuLRVCNQ1J3iVgwY5ndRpZU7n6\n" +
"y8DPH4/4EBT7KvnV\n" +
"-----END PUBLIC KEY-----";
String publicKeyPEM = pubKey
.replace("-----BEGIN PUBLIC KEY-----\n", "")
.replaceAll(System.lineSeparator(), "")
.replace("-----END PUBLIC KEY-----", "");
byte[] publicEncoded = Base64.decodeBase64(publicKeyPEM);
KeyFactory keyFactory1 = KeyFactory.getInstance("DSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicEncoded);
DSAPublicKey pubKeyy = (DSAPublicKey) keyFactory1.generatePublic(publicKeySpec);
// init signer
Signature sig1 = Signature.getInstance("DSA");
sig1.initVerify(pubKeyy);
sig1.update(new byte[]{1});
// verify first result
System.out.println(sig1.verify(HexUtil.decodeHex("first step result")));
}
i tred to use NONEwithDSA within the Java implementation but it didnt do it
Signature sig1 = Signature.getInstance("NONEwithDSA");
java.security.SignatureException: Data for RawDSA must be exactly 20 bytes long
i tred to use SHA1withDSA within the Java implementation but it didnt do it
Signature sig1 = Signature.getInstance("SHA1withDSA");
returns false
In Java the (Signature) algorithm name DSA is an alias for SHA1withDSA, i.e. the original FIPS186-0 algorithm. This is not the same as the nonstandard 'raw' primitive apparently implemented by Go. NONEwithDSA is indeed the correct Java name for what you want, but the implementation in the 'standard' (SUN) provider is something of a kludge that requires exactly 20 bytes of data, not more or less, because that was the size of the SHA1 hash which was the only standard hash for DSA prior to FIPS186-3.
If you (have or can get and) use the BouncyCastle provider, it does not have this restriction, and should work for your code changed to NONEwithDSA (and either the code or security config modified so that BC is selected as the provider, of course).
If you don't use Bouncy, I think you'll have to code the algorithm yourself; I don't think there's any way to get the SUN implementation to do what you want.
Although it would be better to sign a properly-sized hash as specified in the standard, not raw data, and then you could use the Java providers as specified and designed.
The output of tpm_getpubek is of the form:
http://trousers.sourceforge.net/man/tpm_getpubek.8.html
Key Size: 2048 bits
Public Key:
be262286 b51d0a21 88860ae7 32db7478 503c9213 bbb5545a 7e5d7c5f 30ff83da
37c5b548 fee21fd1 650181e8 3401a86b 1462e94e 118fc7f3 eb976b4c eb3a091f
6c5ea72c 527711dc ffbf1ae5 51fcbe1a aec95c64 7e2ac0eb 93484551 339f4959
6332b500 024cfe5c e08697cb 7431b3f4 328b4569 5e2e3eed 93a962d9 8387a58c
df15ecd1 9d01dd08 e2e60002 2baa6197 485dfbfc dd2f1898 fdff3913 7cc3bdc1
cc8bcb04 19e26ac8 466b6daf 4d53e9ea 88e45364 d029c1af b035a354 0f2e4484
e51bc0aa d216cdb6 71f50abb 44a0fdba 38715a6c 0c97d45d 5f3c08ab e7a46117
3666e6b0 d840ee54 4c617388 1714f0a1 acded3bd fc3ea323 8e7d1fcb 9fe74340
1) I am having a hard time to understand how to use it. I am trying to convert it to a java PublicKey object via the code:
byte[] derPublicKey = DatatypeConverter.parseHexBinary(keyBloc);
X509EncodedKeySpec x509publicKey = new X509EncodedKeySpec(derPublicKey);
KeyFactory factory = KeyFactory.getInstance("RSA");
PublicKey publicKey = factory.generatePublic(x509publicKey);
But this returns the following error:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:205)
[java] at java.security.KeyFactory.generatePublic(KeyFactory.java:334)
tpm_getpubek returns the hex-encoded public key modulus in its raw form, not an X509 encoded blob. Assuming you saved the returned data into a string called keyBlock (with whitespace removed) you would generate the public key like this:
PublicKey pk = KeyFactory.getInstance("RSA").generatePublic(
new RSAPublicKeySpec(new BigInteger(keyBlock, 16), new BigInteger("65537")));
I am trying to generate the onion address that is generated from a public key.
If the following line is added to the code in a previous post, just after privateKeyEncoded
String publicKeyEncoded = encoder.encodeToString(publicKey.getEncoded());
When I put the privateKeyEncoded into the /var/lib/tor/hidden_service/private_key, save the publicKeyEncoded and start the tor service, a new onion address is created. I am trying to get the same onion address as the tor service and from one created from the publicKeyEncoded. Using this code
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
//base64 string from the public key created
String publicKeyTest = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMnFkJTMZ2ZxnqLwCiB/EWHjsHbnC+sKEIrGbyOTYiTl3LygsekAX6LhgcllscLUFqSKlMRB3jRB0GAPrIc73E/hTnmWBtF8NT8DhZzl06LZ1BtNjfON1pHm87STMAayiSaXPmSOwIqOA89aJPcA9m4v4IhtjYSFXmCAsE4RqoAwIDAQAB";
//the onion address the tor service gives when the private key is used
String onionAddressTest = "qqkhrc4men3fiqyl";
byte[] publicKeyDecoded = Base64.decodeBase64(publicKeyTest);
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
byte[] sha1hash = messageDigest.digest(publicKeyDecoded);
int numberOfCharacters = 10;
byte[] reducedHash = new byte[numberOfCharacters];
for(int i = 0; i < numberOfCharacters; i++) {
reducedHash[i] = sha1hash[i];
}
Base32 base32encoder = new Base32();
String onionAddress = base32encoder.encodeAsString(reducedHash).toLowerCase();
System.out.println(onionAddress); // but this gives "7j3iz4of464hje2e"
I've tried using spongycastle to replicate my conversion but get the same answer. Which makes me think there's something wrong with how I generate the public key or there's something wrong in my initial conversion from base64.
Given the public key (publicKeyTest) how can you get the onion address (onionAddressTest) using java?
According to this and this you need to hash only the part starting at offset 22 of the X.509 SubjectPublicKeyInfo encoding used by Java which calls it 'X.509' and by OpenSSL which calls it 'PUBKEY'. I can't find any actual Tor doc on this, but I don't believe it can be accidental that this is exactly the beginning of the algorithm-dependent data in SPKI format for an RSA-1024 key:
$ openssl asn1parse -i <49833260.b64
0:d=0 hl=3 l= 159 cons: SEQUENCE
3:d=1 hl=2 l= 13 cons: SEQUENCE
5:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption
16:d=2 hl=2 l= 0 prim: NULL
18:d=1 hl=3 l= 141 prim: BIT STRING
# 18 +3 for DER tag+len +1 for unusedbitcount in BITSTRING = 22
# and the content beginning at 22 is:
$ openssl asn1parse -i -strparse 22 <49833260.b64
0:d=0 hl=3 l= 137 cons: SEQUENCE
3:d=1 hl=3 l= 129 prim: INTEGER :8C9C59094CC6766719EA2F00A207F11
61E3B076E70BEB0A108AC66F23936224E5DCBCA0B1E9005FA2E181C965B1C2D416A48A94C441DE34
41D0600FAC873BDC4FE14E799606D17C353F03859CE5D3A2D9D41B4D8DF38DD691E6F3B4933006B2
8926973E648EC08A8E03CF5A24F700F66E2FE0886D8D84855E6080B04E11AA803
135:d=1 hl=2 l= 3 prim: INTEGER :010001
# which is (exactly) the RSAPublicKey structure from PKCS1
So to do this in Java you can just assume RSA-1024, or with BouncyCastle (and I assume, but haven't tested, spongycastle as well) you can actually parse the ASN.1 properly:
byte[] pubkeyder = Base64.decode("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMnFkJTMZ2ZxnqLwCiB/EWHjsHbnC+sKEIrGbyOTYiTl3LygsekAX6LhgcllscLUFqSKlMRB3jRB0GAPrIc73E/hTnmWBtF8NT8DhZzl06LZ1BtNjfON1pHm87STMAayiSaXPmSOwIqOA89aJPcA9m4v4IhtjYSFXmCAsE4RqoAwIDAQAB");
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
// method 1
byte[] x1 = sha1.digest (Arrays.copyOfRange(pubkeyder, 22, pubkeyder.length));
System.out.println (new String(b32enc(Arrays.copyOf(x1,10))).toLowerCase());
// method 2
byte[] x2 = sha1.digest (SubjectPublicKeyInfo.getInstance(pubkeyder).getPublicKeyData().getOctets());
System.out.println (new String(b32enc(Arrays.copyOf(x2,10))).toLowerCase());
->
qqkhrc4men3fiqyl
qqkhrc4men3fiqyl
I have a bunch of Gemalto java cards and as you see below, I'm okay with mutual authentication process using GlobalPlatformPro:
C:\globalPlatformPro> gp -visa2 -key 47454d5850524553534f53414d504c45 -list -debug -verbose -info
Reader: ACS ACR1281 1S Dual Reader ICC 0
ATR: 3B7D96000080318065B0831111E583009000
A>> 00A40400 00
A<< 6F198408A000000018434D00A50D9F6E061291921101009F6501FF 9000
***** Card info:
A>> 80CA9F7F 00
A<< 9F7F2A4090612812919211010041849D08192420C3033241840333418403344184000003250000000000000000 9000
***** KEY INFO
A>> 80CA00E0 00
A<< E012C00401FF8010C00402FF8010C00403FF8010 9000
VER:255 ID:1 TYPE:DES3 LEN:16
VER:255 ID:2 TYPE:DES3 LEN:16
VER:255 ID:3 TYPE:DES3 LEN:16
Key version suggests factory keys
A>> 80500000 08 2CA286A611F6CAFD 00
A<< 4D0041849D08192420C3FF0131D644E9913234DDE1F0A6A462C71805 9000
A>> 84820100 10 CC2D0CC35F6BD64F816A774D3ADB18F2
A<< 9000
//Useless lines for censored!
C:\globalPlatformPro>
As VISA documents are not publicly available, I took a look at GlobalPlatformPro source code to find out how the key diversification happens in for visa2, and I found these methods there:
public static GPKeySet diversify(GPKeySet keys, byte[] diversification_data, Diversification mode, int scp) throws GPException {
try {
GPKeySet result = new GPKeySet();
Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
for (KeyType v : KeyType.values()) {
if (v == KeyType.RMAC)
continue;
byte [] kv = null;
// shift around and fill initialize update data as required.
if (mode == Diversification.VISA2) {
kv = fillVisa(diversification_data, v);
} else if (mode == Diversification.EMV) {
kv = fillEmv(diversification_data, v);
}
// Encrypt with current master key
cipher.init(Cipher.ENCRYPT_MODE, keys.getKey(v).getKey(Type.DES3));
byte [] keybytes = cipher.doFinal(kv);
// Replace the key, possibly changing type. G&D SCE 6.0 uses EMV 3DES and resulting keys
// must be interpreted as AES-128
GPKey nk = new GPKey(keybytes, scp == 3 ? Type.AES : Type.DES3);
result.setKey(v, nk);
}
return result;
} catch (BadPaddingException |InvalidKeyException | IllegalBlockSizeException e) {
throw new GPException("Diversification failed.", e);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("Diversification failed.", e);
}
}
public static byte[] fillVisa(byte[] init_update_response, KeyType key) {
byte[] data = new byte[16];
System.arraycopy(init_update_response, 0, data, 0, 2);
System.arraycopy(init_update_response, 4, data, 2, 4);
data[6] = (byte) 0xF0;
data[7] = key.getValue();
System.arraycopy(init_update_response, 0, data, 8, 2);
System.arraycopy(init_update_response, 4, data, 10, 4);
data[14] = (byte) 0x0F;
data[15] = key.getValue();
return data;
}
So I tried to repeat the host cryptogram generation for above communication. I have:
Master Key = 47454d5850524553534f53414d504c45
Based on GlobalPlatform v 2.3 Card Specification:
Host_Challenge = 2CA286A611F6CAFD
INITIAL UPDATE response: 4D0041849D08192420C3 FF01 31D644E9913234DD E1F0A6A462C71805
Key Diversification Data = 4D0041849D08192420C3
Key Information = FF01 : So SCP01 is used.
Card Challenge = 31D644E9913234DD
Card Cryptogram = E1F0A6A462C71805
So, based on the GPP source code above:
Diversification_Data = 4D00 9D081924 F001 4D00 9D081924 0F01
And then the Static ENC Key is:
Static_ENC = Encrypt(MasterKey, Diversification_Data )
So, using this online tool, I have:
It means:
Static_ENC_KEY = 84f2a84ecdade8cacc9e7e07faebe4e6
To calculate ENC Session Key, I used GlobalPlatform Specification again:
So I have:
Derivation_Data = 913234DD 2CA286A6 31D644E9 11F6CAFD
And so the ENC_Session_Key is:
ENC_Session_Key = b1ed5ea3f69978274d2ffe0de467ec1c
Finally, The generation and verification of the host cryptogram is performed by concatenating the 8-byte card challenge and 8-byte host challenge resulting in a 16-byte block and concatenating this 16-byte array with 80 00 00 00 00 00 00 00. Then signing this with the ENC session key in CBC mode with a zero ICV:
Data2Encrypt = 31D644E9913234DD 2CA286A611F6CAFD 8000000000000000
And I have:
Well, I tried above steps twice and each time, at the end I was faced with a wrong host_cryptogram value! But when I repeat the steps and wrote those line by line in my question, I finally noticed that the final result that I have is equal with the GPP result at the first of my question! So instead of deleting my question, I preferred to keep it here for future viewers.
So to conclusion:
Having key diversification schemes in a smart card, adds one step to those steps that is mentioned in GlobalPlatform Card Specification for calculating Card Cryptogram and MAC values. And that step is calculating Static Keys.
Diversification_Data for Static Keys calculation are (source):
First two bytes of INITAL UPDATE response data is the same xxh xxh and bytes[4:8] of it are IC Serial Number.
Encrypting Diversification data using Triple DES Algorithm in ECB mode with Master Key, returns Static Keys.
For check the card cryptogram you have to concatenating the 8-byte of host challenge and the 8-byte of card challenge and "8000000000000000".
after make the string, Then signing this with the ENC session key in CBC mode with a zero ICV.
I'm facing the problem that Shiro shows some odd behavior in converting a byte
array to a salt.
I started to implement all classes involved in the process into my application which are:
org.apache.shiro.realm.AuthenticatingRealm
org.apache.shiro.authc.credential.HashedCredentialsMatcher
Upon User creation the user password is hashed with a generated salt and then stored in my database:
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();
String hashedPasswordBase64 = new Sha256Hash(password, salt, 1024).toBase64();
shiro.ini looks like this:
# SALTED JDBC REALM
saltedJdbcRealm=com.mycompany.ssp.SaltedJdbcRealm
dataSource = org.postgresql.ds.PGSimpleDataSource
dataSource.databaseName = Self-Service-Portal
dataSource.serverName = localhost
dataSource.portNumber = 5432
dataSource.user = postgres
dataSource.password = admin
saltedJdbcRealm.dataSource = $dataSource
saltedJdbcRealm.authenticationQuery = SELECT umgmt_users.password, umgmt_users.salt FROM umgmt_users WHERE umgmt_users.user = ?
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
# base64 encoding, not hex in this example:
sha256Matcher.storedCredentialsHexEncoded = false
sha256Matcher.hashIterations = 1024
saltedJdbcRealm.credentialsMatcher = $sha256Matcher
################################################################################
# SECURITY MANAGER #
securityManager.realms = $saltedJdbcRealm
strategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $strategy
################################################################################
my custom saltedJdbcRealm just overrides the doGetAuthenticationInfo. This code is from this blog ->
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//identify account to log to
UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
String username = userPassToken.getUsername();
if (username == null) {
log.debug("Username is null.");
return null;
}
// read password hash and salt from db
PasswdSalt passwdSalt = getPasswordForUser(username);
if (passwdSalt == null) {
log.debug("No account found for user [" + username + "]");
return null;
}
// return salted credentials
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, passwdSalt.password, getName());
info.setCredentialsSalt(new SimpleByteSource(passwdSalt.salt));
return info;
}
Debugging after return info goes like this:
AuthenticatingRealm.java: Mehtod: assertCredentialsMatch()
HashedCredentialsMatcher.java: Method: doCredentialsMatch()
HashedCredentialsMatcher.java: Method: hashProvidedCredentials()
Looking for the error I ended up finding it here in
org.apache.shiro.authc.credential.HashedCredentialsMatcher.java:
protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
Object salt = null;
if (info instanceof SaltedAuthenticationInfo) {
// STOP HERE AND SEE BELOW PART 1!!!
salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
// STOP HERE AND SEE BELOW PART 2!!!
} else {
//retain 1.0 backwards compatibility:
if (isHashSalted()) {
salt = getSalt(token);
}
}
return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
}
Part 1:
lets take a look at the variable info:
The full byte array is the following:
57 109 102 43 65 87 118 88 70 76 105 82 116 104 113 108 116 100 101 108 79 119 61 61
which correctly represents the salt in my database:
9mf+AWvXFLiRthqltdelOw==
Next Step in the code is to extract the Salt from the info variable and store it in the variable salt of type Object.
Part 2:
looking at the variable salt after this line:
salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
executed I get this result:
OW1mK0FXdlhGTGlSdGhxbHRkZWxPdz09
Edit:
I did another example and show you the 2 methods that 1) hash the submitted password 2) take the password from database for comparison & that they are not
the same:
I start off with 2 variables, token (submitted password) & info (stored password information):
Stored Credentials:
credentials:
d5fHxI7kYQYtyqo6kwvZFDATIIsZThvFQeDVidpDDEQ
storedBytes before decoding:
100 53 102 72 120 73 55 107 89 81 89 116 121 113 111 54 107 119 118 90 70 68 65 84 73 73 115 90 84 104 118 70 81 101 68 86 105 100 112 68 68 69 81 61
storedBytes after decoding:
119 -105 -57 -60 -114 -28 97 6 45 -54 -86 58 -109 11 -39 20 48 19 32 -117 25 78 27 -59 65 -32 -43 -119 -38 67 12 68
hash:
7797c7c48ee461062dcaaa3a930bd9143013208b194e1bc541e0d589da430c44
Submitted Credentials:
char[] credentials:
[0] = 1
[1] = 2
[2] = 3
byte[] bytes:
50 69 81 77 57 55 80 53 53 112 89 52 122 69 78 54 57 98 53 56 82 65 61 61
which is 2EQM97P55pY4zEN69b58RA== and this is whats inside the database
cachedBase64:
MkVRTTk3UDU1cFk0ekVONjliNThSQT09
return value is this hash:
af9a7ef0ea9fa4d93eae1ca5d16c03c516f4822ec3e9017f14f694175848a6ab
As the 2 Hash values are not the same I get why my Application is telling me wrong password BUT I created this user with the password 123 using the code above (first code block)
Edit End
So does anyone know why the hash calculation is not giving the same hash value for the same password??? Or what else I might have done wrong (i doubt that the shiro code is wrong so it may be something wrong in my code with generation the password hash/salt or shiro.ini configuration?)
ufff, after a little more playing around with those functions I found the solution why the submitted password is hashed with a wrong salt value
I added 3 lines to the method hashProvidedCredentials inside
org.apache.shiro.authc.credential.HashedCredentialsMatcher.java
protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
Object salt = null;
if (info instanceof SaltedAuthenticationInfo) {
salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
// Get base64 Decoder
java.util.Base64.Decoder Decoder = java.util.Base64.getDecoder();
// decode salt from database
byte[] encodedJava8 = null;
encodedJava8 = Decoder.decode(((SaltedAuthenticationInfo) info).getCredentialsSalt().getBytes());
// save decoded salt value in previous salt Object
salt = ByteSource.Util.bytes(encodedJava8);
// The 3 steps above are nessecary because the Object salt is of type
// SimpleByteSource and:
// - it holds a byte[] which holds the salt in its correct form
// - it also holds a cachedBase64 encoded version of this byte[]
// (which is of course not the actual salt)
// The Problem is that the next method call below that hashes the
// submitted password uses the cachedBase64 value to hash the
// passwort and not the byte[] which represents the actual salt
// Therefor it is nessecary to:
// - create SimpleByteSource salt with the value from the database
// - decode the byte[] so that the cachedBase64 represents the actual salt
// - store the decoded version of the byte[] in the SimpleByteSource variable salt
} else {
//retain 1.0 backwards compatibility:
if (isHashSalted()) {
salt = getSalt(token);
}
}
return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
}
Now the user submitted password on login is hashed the same way as it was when being generated this way:
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();
//Now hash the plain-text password with the random salt and multiple
//iterations and then Base64-encode the value (requires less space than Hex):
String hashedPasswordBase64 = new Sha256Hash(password, salt, 1024).toBase64();
Note: This is not the final version of password hashing. Salt is going to be at least 256bit & iterations are going to be around 200k-300k.
Having the problem solved, I narrowed down the problem to 4 possible options:
1)
There is a major Error in the shiro code (HashedCredentialsMatcher.java) (at least from my point of view it is) because password varification using a salt will always fail this way (see my description inside the code block).
2)
I either uses the wrong CredentialsMatcher for hased & salted passwords and I have no clue which one to use instead.
3)
My Implementation of the doGetAuthenticationInfo Method in my custom Realm has an error. For my Custom Realm I used this tutorial:
Apache Shiro Part 2 - Realms, Database and PGP Certificates
4)
I made a mistake on creation of the password hash (although that code is from the official Apache Shiro Website Link
From my Point of view the options 1 & 4 are not the problem so its either 2 or 3 which cause this problem and make it nessecary to add some code to HashedCredentialsMatcher.java Method: hashProvidedCredentials()
So concluding, does anyone have any idea on this issue just to get clarification??