简体   繁体   中英

What is the C# equivalent of the Java SecretKeySpec

I have following code written in Java

Mac mac = Mac.getInstance("HmacSHA1");
String secretKey ="sKey";
String content ="Hello";

byte[] secretKeyBArr = secretKey.getBytes();    
byte[] contentBArr = content.getBytes();

SecretKeySpec secret_key = new SecretKeySpec(secretKeyBArr,"HmacSHA1");
byte[] secretKeySpecArr = secret_key.getEncoded();

mac.init(secret_key);

byte[] final = mac.doFinal(contentBArr);

I want to make same example in C#. So, I wrote following code

HMACSHA1 hmacsha1 = new HMACSHA1();
string secretKey = "sKey";
string content = "Hello";

byte[] secretKeyBArr = Encoding.UTF8.GetBytes(secretKey);
byte[] contentBArr = Encoding.UTF8.GetBytes(content);

hmacsha1.Key = secretKeyBArr;
byte[] final = hmacsha1.ComputeHash(contentBArr);

Final results are not equal. secretKeyBArr and contentBArr are byte array and their values are same in both example. What is unknown is SecretKeySpec passed to mac.init(). So, what is equivalent same class in C#?

The results are identical, but Java uses signed bytes while C# uses unsigned bytes by default.

Furthermore, SecretKeySpec itself normally does not change the underlying data. You need to eg put a DES key specification in a SecretKeyFactory to make sure that the parity bits are set correctly (in the resulting SecretKey ). So there is no need for an equivalent as the class itself does very little except wrapping the data.

I'm implementing a credit card payment method form a provider (cardinity) that doesn't provide a .net implementation. I'm looking for similar stuff and end-up writing my own as my google skills seem to be ....

What I need is the base64 string of javax.crypto.mac

I am supporting the following methods:

enum EncryptionMethods
{
    None=0,
    HMACSHA1,
    HMACSHA256,
    HMACSHA384,
    HMACSHA512,
    HMACMD5
}

I have implemented the code you have above, the SecretKeySpec and the Mac the following way (you need System.Security.Cryptography.ProtectedData):

internal class Protected
{
    private  Byte[] salt = Guid.NewGuid().ToByteArray();

    protected byte[] Protect(byte[] data)
    {
        try
        {
            return ProtectedData.Protect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
            return null;
        }
    }

    protected byte[] Unprotect(byte[] data)
    {
        try
        {
            return ProtectedData.Unprotect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
            return null;
        }
    }
}


internal class SecretKeySpec:Protected,IDisposable
{
    readonly EncryptionMethods _method;

    private byte[] _secretKey;
    public SecretKeySpec(byte[] secretKey, EncryptionMethods encryptionMethod)
    {
        _secretKey = Protect(secretKey);
        _method = encryptionMethod;
    }

    public EncryptionMethods Method => _method;
    public byte[] SecretKey => Unprotect( _secretKey);

    public void Dispose()
    {
        if (_secretKey == null)
            return;
        //overwrite array memory
        for (int i = 0; i < _secretKey.Length; i++)
        {
            _secretKey[i] = 0;
        }

        //set-null
        _secretKey = null;
    }
    ~SecretKeySpec()
    {
        Dispose();
    }
}

internal class Mac : Protected,IDisposable
{
    byte[] rawHmac;
    HMAC mac;
    public Mac(SecretKeySpec key, string data)
    {

        switch (key.Method)
        {
            case EncryptionMethods.HMACMD5:
                mac = new HMACMD5(key.SecretKey);
                break;
            case EncryptionMethods.HMACSHA512:
                mac = new HMACSHA512(key.SecretKey);
                break;
            case EncryptionMethods.HMACSHA384:
                mac = new HMACSHA384(key.SecretKey);
                break;
            case EncryptionMethods.HMACSHA256:
                mac = new HMACSHA256(key.SecretKey);

            break;
            case EncryptionMethods.HMACSHA1:
                mac = new HMACSHA1(key.SecretKey);
                break;

            default:                    
                throw new NotSupportedException("not supported HMAC");
        }
        rawHmac = Protect( mac.ComputeHash(Cardinity.ENCODING.GetBytes(data)));            

    }

    public string AsBase64()
    {
        return System.Convert.ToBase64String(Unprotect(rawHmac));
    }

    public void Dispose()
    {
        if (rawHmac != null)
        {
            //overwrite memory address
            for (int i = 0; i < rawHmac.Length; i++)
            {
                rawHmac[i] = 0;
            }

            //release memory now
            rawHmac = null;

        }
        mac?.Dispose();
        mac = null;

    }
    ~Mac()
    {
        Dispose();
    }
}

I have implemented this in an OAuthSigner class the following way:

public override string ComputeSignature(string plainTextToEncode, string consumerSecret)
{
    var key = PercentEncode(consumerSecret) + "&";
    try
    {
        using (var secretKey = new SecretKeySpec(key.GetBytes(), EncryptionMethods.HMACSHA1))
        using (Mac mac = new Mac(secretKey, plainTextToEncode))
        {
            return mac.AsBase64();
        }
    }
    finally
    {
        key = null;//free memory, remove sensitive data
    }
}

Then, it's not what you ask for but I need a helper method as I am sending my text to a web service that goes like this and I include it as some might copy the code:

public static String PercentEncode(string textToEncode)
{

    return string.IsNullOrEmpty(textToEncode)
        ?""
        : UrlEncoder.Default.Encode(Cardinity.ENCODING.GetString(Cardinity.ENCODING.GetBytes(textToEncode)))
            .Replace("+", "%20").Replace("*", "%2A")
            .Replace("%7E", "~");

}

The class UrlEncoder comes from System.Text.Encodings.Web, you may have to add a reference.

The class named Cardinity implements a "short-cut" to the Encoding that I use for Cardinity

public abstract class Cardinity
{
    ...
    public static String API_BASE = "https://api.cardinity.com";
    public static String API_VERSION = "v1";
    public static String VERSION = "0.1";
    public static String ENCODING_CHARSET = "UTF-8";
    public static Encoding ENCODING => Encoding.UTF8;
}

as Java uses string.GetBytes a lot, I have added an extension method for this that I call above in the key.GetBytes() , here is the extension code:

public static byte[] GetBytes(this string sender)=>
            Cardinity.ENCODING.GetBytes(sender);

My test method, I have copied the values from Cardinity API passes without any issues.

private OAuthSigner signer;
public HmacOAuthSigner_Test()
{
    signer = new HmacOAuthSigner();
}

[TestMethod]
public void Test_HmacOAuthSigner_ComputeSignature_DefaultText()
{
    var expects = "PxkffxyQh6jsDNcgJ23GpAxs2y8=";
    var test_data = "justsomerandommessage";
    var secretkey = "yvp0leodf231ihv9u29uuq6w8o4cat9qz2nkvs55oeu833s621";

    var actual = signer.ComputeSignature(test_data, secretkey);
    Assert.AreEqual(expects, actual, $"Expecting {test_data} to return {expects} received {actual}");
}

The whole implementation of the HmacOAuthSigner is here, it implements an abstract class with the PercentEncode method in it.

public class HmacOAuthSigner : OAuthSigner
{
    public override string ComputeSignature(string signatureBaseString, string consumerSecret)
    {
        var key = PercentEncode(consumerSecret) + "&";
        var secretKey = new SecretKeySpec(key.GetBytes(), EncryptionMethods.HMACSHA1);
        using (Mac mac = new Mac(secretKey, signatureBaseString))
        {
            return mac.AsBase64();
        }
    }

    public override string GetSignatureMethod()
    {
        return "HMAC-SHA1";
    }
}

and the abstract class that I use as a contract for all the implementations:

public abstract class OAuthSigner
{
    /// <summary>
    /// Signature method used
    /// </summary>
    /// <returns>a string that tells the implementation method</returns>
    public abstract string GetSignatureMethod();

    /// <summary>
    /// computes the signature that is used with the encryption based on the keys provided by cardinity
    /// </summary>
    /// <param name="signatureBaseString">The secret string that services as a base</param>
    /// <param name="consumerSecret">The consumer key as specified in the API settings</param>
    /// <returns>signature string computed by the provided parameters using the signature method</returns>
    public abstract string ComputeSignature(String signatureBaseString, String consumerSecret);

    /// <summary>
    /// Encode a string into a format expected by Cardinity
    /// </summary>
    /// <param name="textToEncode">The text that is to be encoded</param>
    /// <returns>web encoded string ready for using to send to Cardinity</returns>
    public static String PercentEncode(string textToEncode)
    {

        return string.IsNullOrEmpty(textToEncode)
            ?""
            : UrlEncoder.Default.Encode(Cardinity.ENCODING.GetString(Cardinity.ENCODING.GetBytes(textToEncode)))
                .Replace("+", "%20").Replace("*", "%2A")
                .Replace("%7E", "~");

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM