You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Google Tink加密OAuth2令牌时出现间歇性数据损坏导致刷新令牌失败的问题排查

Debugging Your Tink Encryption/Decryption Issues with OAuth Tokens

Let’s break down the problems in your code that are almost certainly causing the intermittent invalid_grant errors when refreshing tokens:

1. Critical Base64 Decoding Bug in reveal()

Your biggest issue is a straightforward mistake in the reveal method:

val encrypted = Base64.decode(encrypted64.encodeToByteArray(), BASE64_ENCODE_SETTINGS)

Here, you’re taking the Base64-encoded string encrypted64, converting it to a UTF-8 byte array, and then trying to Base64-decode that byte array. This is completely incorrect—you should directly pass the encrypted64 string to Base64.decode() instead of converting it to bytes first.

This error means you’re decoding the wrong data entirely. Occasionally this might accidentally produce bytes that can be decrypted (leading to working tokens), but most of the time it will result in corrupted plaintext (like a garbage refresh token), which the server rejects with invalid_grant.

2. Redundant & Risky Base64 Encoding Steps

Your current flow adds unnecessary Base64 layers:

  • conceal(): Plaintext → Base64 → Encrypt → Base64
  • reveal(): Encrypted Base64 → Decode → Decrypt → Base64 Decode

There’s no need to Base64-encode the plaintext before encryption. Tink’s Aead works directly with raw byte arrays, and adding this extra step introduces opportunities for encoding mismatches. For example, using NO_PADDING can lead to issues if any part of the pipeline expects padded Base64 (some servers or libraries handle unpadded Base64 poorly, causing intermittent failures).

3. Potential Base64 Flag Compatibility Issues

Using Base64.NO_WRAP or Base64.NO_PADDING might cause compatibility problems:

  • NO_WRAP is generally safe, but NO_PADDING can lead to decoding failures if the receiving end doesn’t correctly handle unpadded Base64. While unpadded Base64 is technically standard, not all implementations support it reliably, which could explain your intermittent failures.

Fixed Encryption/Decryption Code

Here’s the corrected version of your Security class that addresses all these issues:

import android.util.Base64
import com.google.crypto.tink.Aead
import javax.inject.Inject

// Use standard Base64 flags for better cross-component compatibility
const val BASE64_ENCODE_SETTINGS = Base64.NO_WRAP

data class Security @Inject constructor(private val authenticatedEncryption: Aead) {
    fun conceal(plainText: String, associatedData: String): String {
        // Encrypt raw plaintext bytes directly (no pre-Base64 needed)
        val plainBytes = plainText.encodeToByteArray()
        val associatedBytes = associatedData.encodeToByteArray()
        val encrypted = authenticatedEncryption.encrypt(plainBytes, associatedBytes)
        // Base64-encode encrypted bytes only for safe storage/transmission
        return Base64.encodeToString(encrypted, BASE64_ENCODE_SETTINGS)
    }

    fun reveal(encrypted64: String, associatedData: String): String {
        // Directly decode the Base64 string to get encrypted bytes
        val encrypted = Base64.decode(encrypted64, BASE64_ENCODE_SETTINGS)
        val associatedBytes = associatedData.encodeToByteArray()
        // Decrypt to get raw plaintext bytes
        val decrypted = authenticatedEncryption.decrypt(encrypted, associatedBytes)
        // Convert directly to string (no post-Base64 decode needed)
        return String(decrypted)
    }
}

Additional Checks to Rule Out Other Issues

  • Keyset Persistence: Ensure your AndroidKeysetManager setup is correctly persisting the keyset across app restarts. If keys are being regenerated unexpectedly, decryption of old tokens will fail. Your current manageTink() code looks valid, but double-check that TINK_KEYSET_NAME and PREF_FILE_NAME are consistent throughout your app.
  • Associated Data Consistency: Make sure the associatedData passed to conceal and reveal is exactly the same every time for the same token. If this data changes (e.g., using a dynamic value that varies between calls), decryption will fail outright.

These fixes should eliminate the intermittent decryption failures that are causing the invalid_grant errors from your OAuth server.

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 23:17:38