You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Kotlin中实现与Ruby遗留代码一致的HMAC签名?

Got it, let's break down what's happening here and fix your Kotlin code to match the Ruby behavior exactly.

First, understand what the Ruby code is doing with bin_key = Array(decoded_key).pack('H*')

That line is a bit tricky, so let's unpack it step by step:

  1. decoded_key is the ASCII string you get after Base64-decoding the input ("any carnal pleasure." in your example).
  2. Array(decoded_key) converts the string into an array of individual characters (e.g., ['a', 'n', 'y', ' ', ...]), which is redundant here—joining it back gives the original string.
  3. pack('H*') treats the input string as a hexadecimal source:
    • It filters out any characters that aren't valid hex digits (0-9, a-f, A-F).
    • If the remaining string has an odd length, it appends a 0 to make it even (since hex digits are paired to form bytes).
    • It converts the cleaned hex string into a binary byte array.

Fixing the Kotlin code

We need to replicate that exact hex-to-binary conversion logic for the key. Here's how to do it:

Step 1: Add a helper function to mimic Ruby's pack('H*')

This function will clean the input string, handle odd lengths, and convert it to a byte array:

fun mimicRubyPackHstar(input: String): ByteArray {
    // Filter out non-hex characters (matches Ruby's behavior)
    val cleanedHex = input.replace(Regex("[^0-9a-fA-F]"), "")
    // Add a trailing 0 if length is odd (Ruby does this automatically)
    val evenLengthHex = if (cleanedHex.length % 2 != 0) cleanedHex + "0" else cleanedHex
    // Convert hex string to byte array
    return ByteArray(evenLengthHex.length / 2) { index ->
        val start = index * 2
        val highNibble = evenLengthHex[start].digitToInt(16) shl 4
        val lowNibble = evenLengthHex[start + 1].digitToInt(16)
        (highNibble or lowNibble).toByte()
    }
}

Step 2: Update your main Kotlin logic

Use the helper function to process the key exactly like Ruby does, then compute the HMAC:

import java.nio.charset.Charsets
import java.security.MessageDigest
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64

fun main() {
    val base64Key = "YW55IGNhcm5hbCBwbGVhc3VyZS4="
    // Decode Base64 to bytes, then convert to ASCII string (matches Ruby's string encoding)
    val decodedKeyBytes = Base64.getDecoder().decode(base64Key)
    val decodedKeyStr = String(decodedKeyBytes, Charsets.US_ASCII)
    
    // Process the key to match Ruby's Array(decoded_key).pack('H*')
    val binKey = mimicRubyPackHstar(decodedKeyStr)
    
    val toSign = "some string to sign"
    val hMacSHA256 = Mac.getInstance("HmacSHA256")
    val secretKey = SecretKeySpec(binKey, "HmacSHA256")
    hMacSHA256.init(secretKey)
    
    // Compute HMAC (use UTF-8 for string-to-byte conversion, matches Ruby's default)
    val hmacBytes = hMacSHA256.doFinal(toSign.toByteArray(Charsets.UTF_8))
    
    // Convert to lowercase hex string (matches Ruby's hexdigest output)
    val hmacHex = hmacBytes.joinToString("") { "%02x".format(it) }
    println(hmacHex)
}

Key notes about the alignment with Ruby:

  • String encoding: We use Charsets.US_ASCII to convert the Base64-decoded bytes to a string, which matches Ruby's default encoding for this ASCII-only input.
  • Hex cleaning: The regex filters out non-hex characters exactly as Ruby's pack('H*') does.
  • Odd length handling: We append a 0 for odd-length cleaned hex strings to match Ruby's behavior.
  • Output format: We generate a lowercase hex string, which matches Ruby's hexdigest result.

Running this Kotlin code will produce the exact same HMAC output as your original Ruby code.

内容的提问来源于stack exchange,提问作者freethinker

火山引擎 最新活动