Android端将EC公钥十六进制字符串转换为PublicKey的问题咨询
Got it, let's tackle this issue step by step. The error you're seeing (InvalidKeySpecException with WRONG_TAG) has two main culprits: incorrect KeySpec usage for private keys, and potential edge cases in your hex-to-byte conversion logic. Here's how to fix it:
1. Fix Private Key Parsing (Critical Mistake)
Your getPrivateKey method is using X509EncodedKeySpec for private keys, which is wrong. X509 is designed for public keys—private keys use PKCS#8 encoding instead. That's the primary reason for the WRONG_TAG error, since the ASN.1 parser expects a completely different structure for private key data.
Update your ECDSA.getPrivateKey method to use the correct KeySpec:
public static PrivateKey getPrivateKey(byte[] privk) throws NoSuchAlgorithmException, InvalidKeySpecException { EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privk); // Switch to PKCS8 here KeyFactory kf = KeyFactory.getInstance("EC"); return kf.generatePrivate(privateKeySpec); }
2. Robustify Hex String ↔ Byte Array Conversion
Your current hexStringToByteArray has a minor inefficiency (unnecessary boxing to Integer) and doesn't handle whitespace in hex strings. A more streamlined, safe implementation would look like this:
public static byte[] hexStringToByteArray(String hexString) { hexString = hexString.trim().replaceAll("\\s+", ""); // Clean up any whitespace int len = hexString.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i+1), 16)); } return data; }
This directly computes the byte value without intermediate object creation, and handles signed byte values correctly (e.g., 0x80 converts to -128 as expected).
3. Verify Android EC Key Compatibility
On Android, the default Conscrypt security provider fully supports prime256v1 (also called secp256r1), so your key generation code is already correct. The only thing to confirm is that you're storing the full encoded key (from getEncoded()), which includes the ASN.1 metadata required for reconstruction.
Full Corrected ECDSA Class
Here's the updated class with all fixes, plus integrated robust hex conversion:
import java.security.*; import java.security.spec.*; public class ECDSA { public static KeyPair generateKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1"); SecureRandom random = SecureRandom.getInstanceStrong(); // Prefer strong random over SHA1PRNG keyGen.initialize(ecSpec, random); return keyGen.generateKeyPair(); } public static PublicKey getPublicKey(byte[] pk) throws NoSuchAlgorithmException, InvalidKeySpecException { EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pk); KeyFactory kf = KeyFactory.getInstance("EC"); return kf.generatePublic(publicKeySpec); } public static PrivateKey getPrivateKey(byte[] privk) throws NoSuchAlgorithmException, InvalidKeySpecException { EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privk); KeyFactory kf = KeyFactory.getInstance("EC"); return kf.generatePrivate(privateKeySpec); } // Hex conversion utilities private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); public static String convertBytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars).toLowerCase(); } public static byte[] hexStringToByteArray(String hexString) { hexString = hexString.trim().replaceAll("\\s", ""); int len = hexString.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i+1), 16)); } return data; } }
Usage in MainActivity (With Proper Exception Handling)
Make sure to wrap key operations in try-catch blocks (Android doesn't allow uncaught exceptions on the main thread):
import android.os.Bundle; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; import java.security.*; import java.security.spec.InvalidKeySpecException; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { // Generate key pair KeyPair keyPair = ECDSA.generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); // Convert to hex strings String publicKeyHex = ECDSA.convertBytesToHex(publicKey.getEncoded()); String privateKeyHex = ECDSA.convertBytesToHex(privateKey.getEncoded()); // Reconstruct keys from hex PublicKey pkReconstructed = ECDSA.getPublicKey(ECDSA.hexStringToByteArray(publicKeyHex)); PrivateKey skReconstructed = ECDSA.getPrivateKey(ECDSA.hexStringToByteArray(privateKeyHex)); // Verify reconstruction works (optional debug step) Log.d("ECDSA_TEST", "Public key matches: " + publicKey.equals(pkReconstructed)); Log.d("ECDSA_TEST", "Private key matches: " + privateKey.equals(skReconstructed)); } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeySpecException e) { Log.e("ECDSA_ERROR", "Key operation failed", e); } } }
Why This Works
- Private Key Fix: Using
PKCS8EncodedKeySpectells the KeyFactory to parse the correct ASN.1 structure for private keys, eliminating theWRONG_TAGerror entirely. - Hex Conversion: The revised method avoids unnecessary overhead and handles edge cases like whitespace in hex strings.
- SecureRandom:
getInstanceStrong()ensures you're using a cryptographically secure random source, which is better practice than the implementation-dependentSHA1PRNG.
This should resolve the exception you're seeing on Android 7.0 (API 24) and all newer Android versions.
内容的提问来源于stack exchange,提问作者Sudheesh Singanamalla

