skip to Main Content

I am trying to implement Log in with Telegram (https://core.telegram.org/widgets/login) on my Spring Boot application, but faced a problem.

I’ve been trying to implement PHP code they provided to verify authentication, but something is wrong and I can’t understand what.

So, that’s the code on PHP

secret_key = SHA256(<bot_token>)
if (hex(HMAC_SHA256(data_check_string, secret_key)) == hash) {
  // data is from Telegram
}

Data-check-string is a concatenation of all received fields, sorted in alphabetical order, in the format key=<value> with a line feed character (‘n’, 0xA0) used as separator – e.g., 'auth_date=<auth_date>nfirst_name=<first_name>nid=<id>nusername=<username>.

So, what I did is:

@AllArgsConstructor
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class AuthenticationRequest {

  @NotNull
  private Long authDate;

  private String firstName;

  @NotEmpty
  private String id;

  private String lastName;

  private String photoUrl;

  private String username;

  @NotEmpty
  private String hash;

  @Override
  public String toString() {
    final var data = new StringBuilder();

    for (final Field field : getClass().getDeclaredFields()) {
      try {
        if (!field.getName().equals("hash") && field.get(this) != null) {
          final var fieldName = CaseFormat.LOWER_CAMEL
              .to(CaseFormat.LOWER_UNDERSCORE, field.getName());
          data.append(fieldName).append("=").append(field.get(this)).append("\n");
        }
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    }

    return data.substring(0, data.length() - 2);
  }
}

And these two methods:

private static String hmacSha256(final String data, final byte[] secret) {
    try {
      Mac sha256Hmac = Mac.getInstance("HmacSHA256");
      SecretKeySpec secretKey = new SecretKeySpec(secret, "HmacSHA256");
      sha256Hmac.init(secretKey);

      byte[] signedBytes = sha256Hmac.doFinal(data.getBytes());

      return bytesToHex(signedBytes);
    } catch (NoSuchAlgorithmException | InvalidKeyException ex) {
      return null;
    }
  }

  private static String bytesToHex(byte[] hash) {
    StringBuilder hexString = new StringBuilder();
    for (final byte b : hash) {
      String hex = Integer.toHexString(0xff & b);
      if (hex.length() == 1) {
        hexString.append('0');
      }
      hexString.append(hex);
    }
    return hexString.toString();
  }

And when I compare them, they are completely different two strings

final var telegramData = authenticationRequest.toString();
final var digest = MessageDigest.getInstance("SHA-256");
final var hashedToken = digest.digest(botToken.getBytes());

System.out.println(authenticationRequest.getHash());
System.out.println(hmacSha256(telegramData, hashedToken));

Could you please give me a hint on what I am doing wrong? Maybe I completely misunderstood the way I have to validate the authentication data, or maybe I missed something?

2

Answers


  1. try this, the code is kind of ugly, but it worked well!

    public boolean verifyAuth(JsonObject Telegram_User){
    
        String hash = Telegram_User.remove("hash").getAsString();
    
        try {
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            String[] t = Telegram_User.toString().replace("{","").replace("}","").replace("":","=").replace(",","n").replace(""","").split("n");
            sha256_HMAC.init(new SecretKeySpec(MessageDigest.getInstance("SHA-256").digest(BezouroBot.telegram.getBotToken().getBytes(StandardCharsets.UTF_8)),"SHA256"));
    
            Arrays.sort(t);
            StringBuilder i = new StringBuilder();
            boolean First = true;
    
            for (String s : t) if(First){ First = false; i = new StringBuilder(s);} else i.append("n").append(s);
    
            return Hex.encodeHexString(sha256_HMAC.doFinal(i.toString().getBytes())).equals(hash);
    
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    
    }
    
    Login or Signup to reply.
  2. Here is my implement

    // define your token to a variable
    private final String TELEGRAM_TOKEN = ""
    
    @PostMapping("auth/telegram")
    public ResponseEntity<Object> telegramAuth(@RequestBody Map<String, Object> request) {
        String hash = (String) request.get("hash");
        request.remove("hash");
    
        // Prepare the string
        String str = request.entrySet().stream()
                .sorted((a, b) -> a.getKey().compareToIgnoreCase(b.getKey()))
                .map(kvp -> kvp.getKey() + "=" + kvp.getValue())
                .collect(Collectors.joining("n"));
    
        try {
            SecretKeySpec sk = new SecretKeySpec(
                    // Get SHA 256 from telegram token
                    MessageDigest.getInstance("SHA-256").digest(TELEGRAM_TOKEN.getBytes(StandardCharsets.UTF_8)
                    ), "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(sk);
    
            byte[] result = mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
    
            // Convert the result to hex string
            // Like https://stackoverflow.com/questions/9655181
            String resultStr = ByteBufUtil.bytesToHex(result);
    
            // Compare the result with the hash from body
            if(hash.compareToIgnoreCase(resultStr) == 0) {
    
                // Do other things like create a user and JWT token
                return ResponseEntity.ok("ok");
            } else {
                return ResponseEntity.status(HttpStatus.FORBIDDEN).body(
                        new MessageResponse("Login info hash mismatch")
                );
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(
                    new MessageResponse("Server error while authenticating")
            );
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search