I have a public key (RSA) as a string. I want to use this key to create a secret code, lets say the unencrypted secret code is "TEST TEST" without qoutes.
How can this be achieved? I mean I tried the following but stuck on creating the Key object from the public key string
I used nimbus-jose-jwt library. But wasn't able to create RSA public key object in the following code
// Create the header
val header = JWEHeader(JWEAlgorithm.RSA1_5, EncryptionMethod.A256CBC_HS512)
// Set the plain text
val payload = Payload(decryptedText)
// Create the JWE object and encrypt it
val jweObject = JWEObject(header, payload)
jweObject.encrypt(JWEEncrypter)
// Serialise to compact JOSE form...
val actual = jweObject.serialize()
Also I used another library named org.bitbucket.b_c:jose4j but same thing, I wasn't able to successfully create public key object from the public key string that I got.
Here is the code snippet that I used but failed to achieve what I want and be able to correctly encrypt TEST TEST.
val jwe = JsonWebEncryption()
jwe.payload = decryptedText
jwe.algorithmHeaderValue = KeyManagementAlgorithmIdentifiers.RSA1_5
jwe.encryptionMethodHeaderParameter = ContentEncryptionAlgorithmIdentifiers.AES_256_CBC_HMAC_SHA_512
jwe.key = stringToRSAPublicKey(publicKey)
val serializedJwe = jwe.compactSerialization
The public key string is the following
"MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBGvNkLnetAtR+QSttxIkQ9" +
"mH7pbbjl2UqRu5UDO9kuEiYh4b70JxPN8v1exkuW/FLmxKjdRVq7gNWstumIGm1W" +
"8cf4RtFj88pvZUaVg6NZ21iLAIHtnhb2D/4eBOI8HXdhdZ+bEd+BJbu1rlqm0Rs1" +
"1jzYukR35/n44me3fbP9DH3JmSM8s0F8RmlIY0VqDnSOCOazNupVtJQFWeDIyfcV" +
"/coW+RRrFq5KNwnHPxdl5o3PR3OZgV27H/eBuKxIEGvjBUYchSjAAdJYAnfISvcd" +
"huLeYocZGi5WHEswrQBoUG8GflcdMJTvtTL5PtJG2WdcurIQA6iD2fSdBgQpARJF" +
"AgMBAAE=
In a nutshell, I need to convert this taken from iOS code into android equivalent code:
You should use X509EncodedKeySpec to convert String to Public Key. Here is the code.
val keySpecPublic = X509EncodedKeySpec(Base64.decode(publicKeyString, Base64.DEFAULT))
val publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpecPublic) as RSAPublicKey
Hope it will work.
Related
I'm trying to read a public key (x509 format encoded) from a Java Server to finalize my Elliptic Curve Diffie Hellman Exchange. I can send the Public Key to the server with no problem, but now I want to read the public Key that the server has sent to the iOS Client.
byte[] serverPubKeyEnc = serverKpair.getPublic().getEncoded(); (This is on the server)
This is what I return to the iOS side. For me to deal with it, I need to read it from the input stream and then turn it into a usable public key. This is what I have right now on the iOS side of things to read the key:
var error: Unmanaged<CFError>? = nil
let mutableData = CFDataCreateMutable(kCFAllocatorDefault, CFIndex(0))
if mutableData != nil
{
let headerSize = 26
//For importing Java key data
CFDataAppendBytes(mutableData, CFDataGetBytePtr(data as CFData), CFDataGetLength(data as CFData))
CFDataDeleteBytes(mutableData, CFRangeMake(CFIndex(0), headerSize))
//Use the mutableData here (SecKeyCreateWithData)
let publicKey = SecKeyCreateWithData(
mutableData!,
[
kSecAttrKeyType: kSecAttrKeyTypeEC,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
] as NSDictionary,
&error)
let fullKey = SecKeyCopyExternalRepresentation(publicKey!, &error)
return fullKey!
}
Here I can read the "publicKey", I know it has some value inside it. How can I turn this into a usable key for me to generate the shared secret?
TLDR: I want to read a public key that comes from the Java server (ECDH) to generate a symmetric key for encryption in the iOS client.
The complete flow would look like:
receiving 91 bytes with the public key from server side
create a SecKey with SecKeyCreateWithData
create a key pair on iOS with SecKeyCreateRandomKey
send own public key to server side
server side can compute the shared secret with that information
client computes a shared secret with SecKeyCopyKeyExchangeResult
if everything is correct, it should give the same shared secret on iOS and Java side
Therefore, to get a complete test case, one can write a Java program that generates a key pair. For simplicity, one can copy/paste the public key between the Java and iOS app for a test instead of using a network connection. The Java program writes the public key to the console. This key is copied into the Swift source code. The Swift program is compiled and generates a key pair as well. The public key is copied / pasted to the Java program, which reads it on the console. Both programs then output the calculated shared secret, which should be the same for obvious reasons, since it is used for further symmetric encryption.
This fine answer https://stackoverflow.com/a/26502285/2331445 provides utility methods for converting a hex String to Data and back.
iOS Swift Code
The following code assumes that a secp256r1 curve is used with a key size of 256 bit.
The described flow could be implemented as follows:
let otherKey = "3059301306072a8648ce3d020106082a8648ce3d03010703420004df96b3c0c651707c93418781b91782319f6e798550d954c46ac7318c7eac130f96380991a93049059e03e4190dd147b64d6ebc57320938f026844bda3de22352".hexadecimal!
guard let otherPublicKey = otherPublicKey(data: otherKey) else { return }
guard let ownPrivateKey = createOwnKey() else { return }
guard let ownPublicKey = SecKeyCopyPublicKey(ownPrivateKey) else { return }
send(ownPublicKey: ownPublicKey)
if let sharedSecret = computeSharedSecret(ownPrivateKey: ownPrivateKey, otherPublicKey: otherPublicKey) {
print("shared secret: \(sharedSecret.hexadecimal)")
} else {
print("shared secret computation failed")
}
The used functions:
private func otherPublicKey(data: Data) -> SecKey? {
var error: Unmanaged<CFError>? = nil
let cfData = data.dropFirst(26) as CFData
let attributes = [
kSecAttrKeyType: kSecAttrKeyTypeEC,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
] as CFDictionary
if let publicKey = SecKeyCreateWithData(cfData, attributes, &error) {
return publicKey
}
print("other EC public: \(String(describing: error))")
return nil
}
private func createOwnKey() -> SecKey? {
var error: Unmanaged<CFError>? = nil
let keyPairAttr: [String : Any] = [kSecAttrKeySizeInBits as String: 256,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false]
]
guard let key = SecKeyCreateRandomKey(keyPairAttr as CFDictionary, &error) else {
print("key creation: \(String(describing: error))")
return nil
}
return key
}
This function send only outputs the key in hex on the debug console. For the test it can be transferred to the Java program via copy/paste. In a real program it would be transferred to the server via a network connection.
private func send(ownPublicKey: SecKey) {
guard let data = SecKeyCopyExternalRepresentation(ownPublicKey, nil) as Data? else {
print("SecKeyCopyExternalRepresentation failed")
return
}
let secp256r1Header = "3059301306072a8648ce3d020106082a8648ce3d030107034200"
let pkWithHeader = secp256r1Header + data.hexadecimal
print("ownPublicKeyHexWithHeader \(pkWithHeader.count / 2) bytes: " + pkWithHeader)
}
With the own private key and the public key of the server, the shared secret can be computed.
private func computeSharedSecret(ownPrivateKey: SecKey, otherPublicKey: SecKey) -> Data? {
let algorithm:SecKeyAlgorithm = SecKeyAlgorithm.ecdhKeyExchangeStandard
let params = [SecKeyKeyExchangeParameter.requestedSize.rawValue: 32, SecKeyKeyExchangeParameter.sharedInfo.rawValue: Data()] as [String: Any]
var error: Unmanaged<CFError>? = nil
if let sharedSecret: Data = SecKeyCopyKeyExchangeResult(ownPrivateKey, algorithm, otherPublicKey, params as CFDictionary, &error) as Data? {
return sharedSecret
} else {
print("key exchange: \(String(describing: error))")
}
return nil
}
Test
In the upper area you can see the Xcode console and in the lower area the output of the Java program. The common secret is the same. So the test was successful.
I need to sign some data with one private key using Algorithm SHA1RSA ,Rsa Key length 2048 with 64 base encoding.My code is like this
string sPayload = "";
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("URI");
httpWebRequest.ContentType = "application/json; charset=utf-8";
httpWebRequest.Method = WebRequestMethods.Http.Post;
using (StreamWriter streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
sPayload = "{\"id\":\"14123213213\"," +
"\"uid\":\"teller\"," +
"\"pwd\":\"abc123\"," +
"\"apiKey\":\"2343243\"," +
"\"agentRefNo\":\"234324324\"}";
httpWebRequest.Headers.Add("SIGNATURE", Convert.ToBase64String(new System.Security.Cryptography.SHA1CryptoServiceProvider().ComputeHash(Encoding.ASCII.GetBytes(sPayload))));
streamWriter.Write(sPayload);
streamWriter.Flush();
streamWriter.Close();
}
System.Net.ServicePointManager.Expect100Continue = false;
HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
string result = streamReader.ReadToEnd();
}
In the Header name Signature i need to pass the signed data(sPayload) using the private key.But using above code an error is getting as "invalid signature" from third party and i'am not sure whether the Encryption part is correct or not.
httpWebRequest.Headers.Add("SIGNATURE", Convert.ToBase64String(new System.Security.Cryptography.SHA1CryptoServiceProvider().ComputeHash(Encoding.ASCII.GetBytes(sPayload))));
Third party had provide one certificate(cert,sha1) and key.should i refer that to the code?
You have computed the SHA-1 hash of sPayload, not the RSA-SHA1 signature.
If you have an X509Certificate2:
using (RSA rsa = cert.GetRSAPrivateKey())
{
return rsa.SignData(sPayload, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
}
If you already have a raw RSA key then just leave off the using statement.
If you have to compute the hash of sPayload for some other reason you can do it like
byte[] hash;
byte[] signature;
using (HashAlgorithm hasher = SHA1.Create())
using (RSA rsa = cert.GetRSAPrivateKey())
{
hash = hasher.ComputeHash(sPayload);
signature = rsa.SignHash(hash, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
}
SignHash still requires the HashAlgorithmName value because the algorithm identifier is embedded within the signature.
I am trying to implement ECDH encryption/decryption along with JWE in Android (Java).
I have found the jose4j and Nimbus JOSE libraries that aim to do everything I need but appears that it's more challenging than I thought.
If anybody is familiar then it's for 3D Secure 2.0...
In the spec below:
SDK = the local side
DS = Directory Server (the other side)
Next is the spec:
Given: P(DS) - an EC public key (provided in PEM format, can be transformed to PublicKey or to JWK)
Generate a fresh ephemeral key pair (Q(SDK), d(SDK))
Conduct a Diffie-Hellman key exchange process according to JWA (RFC7518) in Direct Key Agreement mode using curve P-256, d(SDK) and P(DS) to produce a CEK. The parameter values supported in this version of the specification are:
"alg":ECDH-ES
"apv":DirectoryServerID
"epk":P(DS),inJSONWebKey(JWK)format {"kty":"EC", "crv":"P-256"}
All other parameters: not present
CEK:"kty":oct-256bits
Generate 128-bit random data as IV
Encrypt the JSON object according to JWE (RFC7516) using the CEK and JWE Compact Serialization. The parameter values supported in this version of the specification are:
"alg":dir
"epk":Q(SDK) as {"kty": "EC", "crv": "P-256"}
"enc":either"A128CBC-HS256"or"A128GCM"
All other parameters: not present
If the algorithm is A128CBC-HS256 use the full CEK or if the algorithm is A128GCM use the leftmost 128 bits of the CEK.
Delete the ephemeral key pair (Q(SDK),d(SDK))
Makes the resulting JWE available to the 3DS Server as SDK Encrypted Data
If someone has implemented this exact spec and can share the code this would be brilliant!!
There's an example of creating JWT using ECDH in the examples of jose4j:
https://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples (the last example, titled as "Producing and consuming a nested (signed and encrypted) JWT").
But this example is not exactly what I need. It creates a token while I need to encrypt a text.
Starting from "CEK:"kty":oct-256bits" in the spec above I don't understand what to do.
Here's my code (so far) using Nimbus lib:
public String nimbus_encrypt(String plainJson, ECPublicKey otherPublicKey, String directoryServerId) throws JOSEException {
JWEHeader jweHeader = new JWEHeader(
JWEAlgorithm.ECDH_ES,
EncryptionMethod.A128CBC_HS256,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Base64URL.encode(directoryServerId),
null,
0,
null,
null,
null,
null);
JWEObject jwe = new JWEObject(jweHeader, new Payload(plainJson));
jwe.encrypt(new ECDHEncrypter(otherPublicKey));
String serializedJwe = jwe.serialize();
Log.d("[ENCRYPTION]", "nimbus_encrypt: jwe = " + jwe.getHeader());
Log.d("[ENCRYPTION]", "nimbus_encrypt: serializedJwe = " + serializedJwe);
return serializedJwe;
}
This is the nimbus output:
nimbus_encrypt: jwe = {"epk":{"kty":"EC","crv":"P-256","x":"AS0GRfAOWIDONXxaPR_4IuNHcDIUJPHbACjG5L7x-nQ","y":"xonFn1vRASKUTdCkFTwsl16LRmSe-bAF8EO4-mh1NYw"},"apv":"RjAwMDAwMDAwMQ","enc":"A128CBC-HS256","alg":"ECDH-ES"}
nimbus_encrypt: serializedJwe = eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJBUzBHUmZBT1dJRE9OWHhhUFJfNEl1TkhjRElVSlBIYkFDakc1TDd4LW5RIiwieSI6InhvbkZuMXZSQVNLVVRkQ2tGVHdzbDE2TFJtU2UtYkFGOEVPNC1taDFOWXcifSwiYXB2IjoiUmpBd01EQXdNREF3TVEiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiRUNESC1FUyJ9..Pi48b7uj3UilvVXKewFacg.0sx9OkHxxtZvkVm-IENRFw.bu5GvOAwcZxdxaDKWIBqwA
Here's my code (so far, using #Brian-Campbell's answer) using jose4j lib:
public String jose4j_encrypt(String plainJson, PublicKey otherPublicKey, String directoryServerId) throws JoseException {
JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.ECDH_ES);
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);
jwe.setHeader(HeaderParameterNames.AGREEMENT_PARTY_V_INFO, Base64Url.encodeUtf8ByteRepresentation(directoryServerId));
jwe.setKey(otherPublicKey);
jwe.setPayload(plainJson);
String serializedJwe = jwe.getCompactSerialization();
Log.d("[ENCRYPTION]", "jose4j_encrypt: jwe = " + jwe);
Log.d("[ENCRYPTION]", "jose4j_encrypt: serializedJwe = " + serializedJwe);
return serializedJwe;
}
This is the jose4j output:
jose4j_encrypt: jwe = JsonWebEncryption{"alg":"ECDH-ES","enc":"A128CBC-HS256","apv":"RjAwMDAwMDAwMQ","epk":{"kty":"EC","x":"prvyhexJXDWvPQmPA1xBjY8mkHEbrEiJ4Dr-7_5YfdQ","y":"fPjw8UdfzgkVTppPSN5o_wprItKLwecoia9yrWi38yo","crv":"P-256"}}
jose4j_encrypt: serializedJwe = eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImFwdiI6IlJqQXdNREF3TURBd01RIiwiZXBrIjp7Imt0eSI6IkVDIiwieCI6InBydnloZXhKWERXdlBRbVBBMXhCalk4bWtIRWJyRWlKNERyLTdfNVlmZFEiLCJ5IjoiZlBqdzhVZGZ6Z2tWVHBwUFNONW9fd3BySXRLTHdlY29pYTl5cldpMzh5byIsImNydiI6IlAtMjU2In19..gxWYwFQSOqLk5HAgs7acdA.mUIHBiWpWSlQaEOJ_EZGYA.eiTe-88fw-Jfuhji_W0rtg
As can be seen the "alg" header in the final result is "ECDH-ES" and not "dir" as required.
If I would implement both sides of the communication then it would be enough, but with this spec seems like many configurations are missing here...
Code using jose4j is longer and seems more configurable but I couldn't construct something valuable enough to post here.
The main missing part for me is how to generate the CEK from the spec above.
Thank you.
EDIT
Added jose4j code above and added the outputs...
Below is some example code using jose4j that I think does what you're looking for. The example you pointed to is similar with the plaintext of the JWE being a JWS/JWT but it can be any arbitrary content. The details of the CEK generation/derivation are taken care of by the underlying JWE functionality. Please note that this only encrypts the content and doesn't provide integrity protection or sender authentication.
String encodedCert = "MIIBRjCB7KADAgECAgYBaqxRCjswDAYIKoZIzj0EAwIFADApMQswCQYDVQQGEwJDQTEMMAoGA1UE\n" +
"ChMDbWVoMQwwCgYDVQQDEwNtZWgwHhcNMTkwNTEyMTM1MjMzWhcNMjAwNTExMTM1MjMzWjApMQsw\n" +
"CQYDVQQGEwJDQTEMMAoGA1UEChMDbWVoMQwwCgYDVQQDEwNtZWgwWTATBgcqhkjOPQIBBggqhkjO\n" +
"PQMBBwNCAAQH83AhYHCehKj7M5+UTNshwLFqqqJWGrJPNj9Kr7xvxtcZnyjq+AKLGMLfdk/G7yb8\n" +
"4vIh0cJwtVs70WgIXT8xMAwGCCqGSM49BAMCBQADRwAwRAIgO0PJRzan2msHpcvcqhybzeualDea\n" +
"/X2QGAWCYT+sNiwCIDMrfhrzUQ6uIX4vnB8AYqb85Ssl7Qcl9nYtjHb08NR8";
X509Util x509Util = new X509Util();
X509Certificate x509Certificate = x509Util.fromBase64Der(encodedCert);
// the JWE object
JsonWebEncryption jwe = new JsonWebEncryption();
// The output of the ECDH-ES key agreement will be used as the content encryption key
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.ECDH_ES);
// The content encryption key is used to encrypt the payload
// with a composite AES-CBC / HMAC SHA2 encryption algorithm
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);
// don't think you really need this but you had ""apv":DirectoryServerID" in the question so...
jwe.setHeader(HeaderParameterNames.AGREEMENT_PARTY_V_INFO, Base64Url.encodeUtf8ByteRepresentation("<<DirectoryServerID>>"));
// We encrypt to the receiver using their public key
jwe.setKey(x509Certificate.getPublicKey());
// and maybe put x5t to help the receiver know which key to use in decryption
jwe.setX509CertSha1ThumbprintHeaderValue(x509Certificate);
// What is going to be encrypted
jwe.setPayload("Your text here. It can be JSON or whatever.");
// Produce the JWE compact serialization, which is a string consisting of five dot ('.') separated
// base64url-encoded parts in the form Header..IV.Ciphertext.AuthenticationTag
String serializedJwe = jwe.getCompactSerialization();
System.out.println(serializedJwe);
I'd like to sign a file by using a RSA keypair. For this purpose I have this Perl script:
#!/usr/bin/perl
use Crypt::RSA;
my $data = ... # File contents
my $rsa = new Crypt::RSA;
my $key = new Crypt::RSA::Key::Private(Filename => "stackoverflow.priv", Password => "*****");
my $signature = $rsa->sign(Message => $data, Key => $key, Armour => 0);
# Write signature to file
On the client side, I'd like to use the following Java function to verify the file:
private static final String PUBLICKEY_MOD = "190343051422614110006523776876348493...";
private static String PUBLICKEY_EXP = "65537";
public boolean check() {
byte[] data = ... // Data
byte[] dataSignature = ... // Signature (as calculated in the Perl script)
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(getPublicKey());
signature.update(data);
return signature.verify(dataSignature);
}
private PublicKey getPublicKey() {
RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(PUBLICKEY_MOD), new BigInteger(PUBLICKEY_EXP));
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
However, check() always reports false. These things I already checked:
data and dataSignature are correctly read
PUBLICKEY_MOD and PUBLICKEY_EXP are correct
getPublicKey() returns a PublicKey which has the correct attributes
the private key and the public key are part of the same pair
Does anyone know how to verify the file correctly? Is signature correctly instanced?
Your first clue that something might be wrong is that you never tell Perl what hash function to use, but you tell Java to use SHA256. You have a lot of work to do on the Perl side. Also, the default padding scheme for Crypt::RSA seems to be PSS, whereas for Java it is PKCSv1.5
This is a follow up to this question, but I'm trying to port C# code to Java instead of Ruby code to C#, as was the case in the related question. I am trying to verify the encrypted signature returned from the Recurly.js api is valid. Unfortunately, Recurly does not have a Java library to assist with the validation, so I must implement the signature validation myself.
Per the related question above (this), the following C# code can produce the hash needed to validate the signature returned from Recurly:
var privateKey = Configuration.RecurlySection.Current.PrivateKey;
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(dataToProtect));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
Recurly provides the following example data on their signature documentation page:
unencrypted verification message:
[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
private key:
0123456789ABCDEF0123456789ABCDEF
resulting signature:
0f5630424b32402ec03800e977cd7a8b13dbd153-1312701386
Here is my Java implementation:
String unencryptedMessage = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
String encryptedMessage = getHMACSHA1(unencryptedMessage, getSHA1(privateKey));
private static byte[] getSHA1(String source) throws NoSuchAlgorithmException, UnsupportedEncodingException{
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] bytes = md.digest(source.getBytes("UTF-8"));
return bytes;
}
private static String getHMACSHA1(String baseString, byte[] keyBytes) throws GeneralSecurityException, UnsupportedEncodingException {
SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] bytes = baseString.getBytes("ASCII");
return Hex.encodeHexString(mac.doFinal(bytes));
}
However, when I print out the encryptedMessage variable, it does not match the message portion of the example signature. Specifically, I get a value of "c8a9188dcf85d1378976729e50f1de5093fabb78" instead of "0f5630424b32402ec03800e977cd7a8b13dbd153".
Update
Per #M.Babcock, I reran the C# code with the example data, and it returned the same output as the Java code. So it appears my hashing approach is correct, but I am passing in the wrong data (unencryptedMessage). Sigh. I will update this post if/when I can determine what the correct data to encrypt is- as the "unencrypted verification message" provided in the Recurly documentation appears to be missing something.
Update 2
The error turned out to be the "unencrypted verification message" data/format. The message in the example data does not actually encrypt to the example signature provided- so perhaps outdated documentation? At any rate, I have confirmed the Java implementation will work for real-world data. Thanks to all.
I think the problem is in your .NET code. Does Configuration.RecurlySection.Current.PrivateKey return a string? Is that value the key you expect?
Using the following code, .NET and Java return identical results.
.NET Code
string message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
string privateKey = "0123456789ABCDEF0123456789ABCDEF";
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(message));
Console.WriteLine(" Message: {0}", message);
Console.WriteLine(" Key: {0}\n", privateKey);
Console.WriteLine("Key bytes: {0}", BitConverter.ToString(hashedKey).Replace("-", "").ToLower());
Console.WriteLine(" Result: {0}", BitConverter.ToString(hash).Replace("-", "").ToLower());
Result:
Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
Key: 0123456789ABCDEF0123456789ABCDEF
Key bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
Result: c8a9188dcf85d1378976729e50f1de5093fabb78
Java
String message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] keyBytes = md.digest(privateKey.getBytes("UTF-8"));
SecretKey sk = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(sk);
byte[] result = mac.doFinal(message.getBytes("ASCII"));
System.out.println(" Message: " + message);
System.out.println(" Key: " + privateKey + "\n");
System.out.println("Key Bytes: " + toHex(keyBytes));
System.out.println(" Results: " + toHex(result));
Result:
Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
Key: 0123456789ABCDEF0123456789ABCDEF
Key Bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
Results: c8a9188dcf85d1378976729e50f1de5093fabb78
I suspect the default encoding of the values you're working on may be different. As they do not have it specified, they will use the default encoding value of the string based on the platform you're working on.
I did a quick search to verify if this was true and it was still inconclusive, but it made me think that strings in .NET default to UTF-16 encoding, while Java defaults to UTF-8. (Can someone confirm this?)
If such's the case, then your GetBytes method with UTF-8 encoding is already producing a different output for each case.
Based on this sample code, it looks like Java expects you to have not already SHA1'd your key before creating a SecretKeySpec. Have you tried that?