简体   繁体   中英

Login with Telegram with Spring Security

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 – eg, '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?

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

}

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

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