skip to Main Content

For the sake of me, I can’t get to validate a Telegram hash in Java.

I have a working JS example compiled from different sources that I’ve reduced to barebones to illustrate this question.

function main() {
  var k = CryptoJS.HmacSHA256('k', 'WebAppData');
  console.log("k: " + k);

  var h = CryptoJS.HmacSHA256('data', k);
  console.log("h: " + h);
}

main();
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

The response of the complete example matches the expected hash

"k: 3db03151d3fb2678c9fc0f24ee15817bfd1ff922a712a055f63066f875e43fc1"
"h: 9158b0aeb1e882c075ea46a0d5302cffba1a9b66b0df40f5f01eef614f7d9f94"

Trying to do the same thing using Apache commons’ Hmac method

var secretKey = new HmacUtils("HmacSHA256", "WebAppData").hmacHex("k");
var hash = new HmacUtils("HmacSHA256", secretKey).hmacHex("data");

I end up with the first one being correct but not the second one, that differs from the other implementation.

secretKey: 3db03151d3fb2678c9fc0f24ee15817bfd1ff922a712a055f63066f875e43fc1
hash: 2c73ad3290e04a382b29edea6cb0ef4b1454592429336c32e98ad611452581b2

what am I failing to see?

Isn’t there a method to handle this validation in the Telegram SDK I missed to avoid doing this by hand?

2

Answers


  1. The problem is that you take the hex-encoded form of the secret key as the key of the hash. The HmacUtils(String, String) constructor will encode the string to bytes using UTF-8, which is not what you want here, so you need to use the HmacUtils(String, byte[]) constructor.

    So, you need to use the raw byte form:

    byte[] secretKey = new HmacUtils("HmacSHA256", "WebAppData").hmac("k");
    String hash = new HmacUtils("HmacSHA256", secretKey).hmacHex("data");
    
    System.out.println(HexFormat.of().formatHex(secretKey));
    System.out.println(hash);
    

    Output:

    3db03151d3fb2678c9fc0f24ee15817bfd1ff922a712a055f63066f875e43fc1
    9158b0aeb1e882c075ea46a0d5302cffba1a9b66b0df40f5f01eef614f7d9f94
    

    This matches the hash from the JavaScript code.

    Login or Signup to reply.
  2. Here is the full class for validation:

    public class TelegramAuth {
        boolean isValid(String telegramInitData, String botToken) throws Exception {
            Pair<String, String> result = parseInitData(telegramInitData);
            String hash = result.getFirst();
            String initData = result.getSecond();
            byte[] secretKey = new HmacUtils("HmacSHA256", "WebAppData").hmac(botToken);
            String initDataHash = new HmacUtils("HmacSHA256", secretKey).hmacHex(initData);
    
            return initDataHash.equals(hash);
        }
    
        private Pair<String, String> parseInitData(String telegramInitData) throws UnsupportedEncodingException {
            Map<String, String> initData = parseQueryString(telegramInitData);
            initData = sortMap(initData);
            String hash = initData.remove("hash");
    
            List<String> separatedData = initData.entrySet().stream().map((v) -> v.getKey() + "=" + v.getValue()).toList();
            return Pair.of(hash, String.join("n", separatedData));
    
        }
    
        private Map<String, String> parseQueryString(String queryString) throws UnsupportedEncodingException {
            Map<String, String> parameters = new TreeMap<>();
            String[] pairs = queryString.split("&");
            for (String pair : pairs) {
                int idx = pair.indexOf("=");
                String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8) : pair;
                String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8) : null;
                parameters.put(key, value);
            }
            return parameters;
        }
    
        private Map<String, String> sortMap(Map<String, String> map) {
            return new TreeMap<>(map);
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search