【问题标题】:What is the C# equivalent of the Java SecretKeySpecJava SecretKeySpec 的 C# 等价物是什么
【发布时间】:2012-12-11 22:09:31
【问题描述】:

我有以下用 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);

我想用 C# 做同样的例子。所以,我写了以下代码

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);

最终结果不相等。 secretKeyBArr 和 contentBArr 是字节数组,它们的值在两个示例中相同。未知的是传递给 mac.init() 的 SecretKeySpec。那么,什么是 C# 中的等价类?

【问题讨论】:

  • 您可能想要编辑问题的标题以表示关于 java SecretKeySpec 的 C# 等价物是什么的实际问题
  • 秘钥有多长?
  • 我想SecretKeySpec 除了在"HmacSha1" 的情况下存储字节之外并没有做太多事情。问题可能出在其他地方。
  • PS 你如何比较结果?
  • 在 Java 和 C# 示例中只读取字节数组。

标签: java c# cryptography sha1 hmac


【解决方案1】:

结果是相同的,但 Java 使用有符号字节,而 C# 默认使用无符号字节。

此外,SecretKeySpec 本身通常不会更改基础数据。你需要例如将 DES 密钥规范放入 SecretKeyFactory 以确保奇偶校验位设置正确(在结果 SecretKey 中)。所以不需要等价物,因为类本身除了包装数据外几乎没有什么作用。

【讨论】:

  • OK,但 Java 代码现在使用默认字符编码。如果返回与 UTF-8 不同的编码(对于 ASCII 文本不太可能,但对于其他文本很可能),那么比较将失败。
  • 刚才我试过了。我获得与使用 getBytes(Charset.forName("UTF-8")) 之前相同的值
  • 我希望的结果中没有'['字符?
  • 没有。我比较了 VS 和 Eclipse 中的最终字节数组。
  • 没错。将 C# 中的字节数组转换为有符号字节数组解决了这个问题。
【解决方案2】:

我正在从不提供 .net 实现的提供商 (cardinity) 实施信用卡支付方式。我正在寻找类似的东西并最终编写自己的东西,因为我的谷歌技能似乎......

我需要的是javax.crypto.mac的base64字符串

我支持以下方法:

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

我已经按照以下方式实现了您上面的代码、SecretKeySpec 和 Mac(您需要 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();
    }
}

我已经通过以下方式在 OAuthSigner 类中实现了这一点:

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
    }
}

然后,这不是您要求的,但我需要一个辅助方法,因为我将文本发送到这样的网络服务,并且我将其包含在内,因为有些人可能会复制代码:

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", "~");

}

UrlEncoder 类来自 System.Text.Encodings.Web,您可能需要添加引用。

名为 Cardinity 的类实现了我用于 Cardinity 的 Encoding 的“捷径”

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;
}

由于 Java 经常使用 string.GetBytes,我为此添加了一个扩展方法,我在上面的 key.GetBytes() 中调用了它,这里是扩展代码:

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

我的测试方法,我从 Cardinity API 复制了值,没有任何问题。

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}");
}

HmacOAuthSigner 的完整实现在这里,它实现了一个带有 PercentEncode 方法的抽象类。

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";
    }
}

以及我用作所有实现合同的抽象类:

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", "~");

    }
}

【讨论】:

    猜你喜欢
    • 2011-10-13
    • 1970-01-01
    • 2017-12-18
    • 2011-01-10
    • 2016-07-26
    • 2010-12-07
    • 2010-11-22
    • 1970-01-01
    相关资源
    最近更新 更多