Android P 上的 ECDSA 数字签名验证
ECDSA Digital Signature Verification on Android P
import android.os.Bundle;
import android.util.Base64;
import android.widget.Toast;
import org.bouncycastle.jce.provider.BouncyCastleProvider; // implementation 'org.bouncycastle:bcprov-jdk16:1.46'
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
static final String PUBLIC_KEY = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEMEV3EPREEDc0t4MPeuYgreLMHMVfD7iYJ2Cnkd0ucwf3GYVySvYTttMVMNMEKF554NYmdrOlqwo2s8J2tKt/oQ==";
static final String DATA = "Hello";
static final String SIGNATURE = "MEUCIQCsuI4OcBAyA163kiWji1lb7xAtC8S0znf62EpdA+U4zQIgBcLbXtcuxXHcwQ9/DmiVfoiigKnefeYgpVXZzjIuYn8=";
static boolean verifyData() throws Exception {
PublicKey pk = getPublicKey();
byte[] signatureBytes = Base64.decode(SIGNATURE, Base64.NO_WRAP);
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
signature.initVerify(pk);
signature.update(DATA.getBytes("UTF-8"));
return signature.verify(signatureBytes);
}
static PublicKey getPublicKey() throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decode(PUBLIC_KEY, Base64.NO_WRAP));
PublicKey key = keyFactory.generatePublic(x509EncodedKeySpec);
return key;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Toast.makeText(this, verifyData() + "", Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(this, "Failure: " + e.getMessage(), Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
}
以上代码执行ECDSA数字签名验证。此代码在 Android P 以下的每个 android 版本上都能正常工作。在 Android P 上我得到这个异常。
java.security.NoSuchAlgorithmException: The BC provider no longer provides an implementation for Signature.SHA1withRSA. Please see https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html for more details.
如前所述here我试图从这些语句中删除提供者参数,如下所示:
Signature signature = Signature.getInstance("SHA256withECDSA");
KeyFactory keyFactory = KeyFactory.getInstance("EC");
但是没有解决问题?怎么会?现在我得到这个例外:
java.security.InvalidKeyException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: Error parsing public key
一种可能的解决方案是将 targetSdkVersion 降低到 27,但这还不够好。有更好的解决方法吗?
我可以在我的机器上重现该问题(Android P,API 级别 28)。该问题的一种可能解决方案是在添加 BC 提供程序之前删除 pre-installed 版本:
Security.removeProvider("BC");
Security.addProvider(new BouncyCastleProvider());
或者:
Security.removeProvider("BC");
Security.insertProviderAt(new BouncyCastleProvider(), 1);
此外,必须明确指定 BC 提供商:
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
...
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
在这个星座中可以验证签名。
与较低 API 级别相比的不同行为可能与在 Android P、here.
中实施的有关 BC Provider 的更改有关
import android.os.Bundle;
import android.util.Base64;
import android.widget.Toast;
import org.bouncycastle.jce.provider.BouncyCastleProvider; // implementation 'org.bouncycastle:bcprov-jdk16:1.46'
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
static final String PUBLIC_KEY = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEMEV3EPREEDc0t4MPeuYgreLMHMVfD7iYJ2Cnkd0ucwf3GYVySvYTttMVMNMEKF554NYmdrOlqwo2s8J2tKt/oQ==";
static final String DATA = "Hello";
static final String SIGNATURE = "MEUCIQCsuI4OcBAyA163kiWji1lb7xAtC8S0znf62EpdA+U4zQIgBcLbXtcuxXHcwQ9/DmiVfoiigKnefeYgpVXZzjIuYn8=";
static boolean verifyData() throws Exception {
PublicKey pk = getPublicKey();
byte[] signatureBytes = Base64.decode(SIGNATURE, Base64.NO_WRAP);
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
signature.initVerify(pk);
signature.update(DATA.getBytes("UTF-8"));
return signature.verify(signatureBytes);
}
static PublicKey getPublicKey() throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decode(PUBLIC_KEY, Base64.NO_WRAP));
PublicKey key = keyFactory.generatePublic(x509EncodedKeySpec);
return key;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Toast.makeText(this, verifyData() + "", Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(this, "Failure: " + e.getMessage(), Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
}
以上代码执行ECDSA数字签名验证。此代码在 Android P 以下的每个 android 版本上都能正常工作。在 Android P 上我得到这个异常。
java.security.NoSuchAlgorithmException: The BC provider no longer provides an implementation for Signature.SHA1withRSA. Please see https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html for more details.
如前所述here我试图从这些语句中删除提供者参数,如下所示:
Signature signature = Signature.getInstance("SHA256withECDSA");
KeyFactory keyFactory = KeyFactory.getInstance("EC");
但是没有解决问题?怎么会?现在我得到这个例外:
java.security.InvalidKeyException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: Error parsing public key
一种可能的解决方案是将 targetSdkVersion 降低到 27,但这还不够好。有更好的解决方法吗?
我可以在我的机器上重现该问题(Android P,API 级别 28)。该问题的一种可能解决方案是在添加 BC 提供程序之前删除 pre-installed 版本:
Security.removeProvider("BC");
Security.addProvider(new BouncyCastleProvider());
或者:
Security.removeProvider("BC");
Security.insertProviderAt(new BouncyCastleProvider(), 1);
此外,必须明确指定 BC 提供商:
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
...
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
在这个星座中可以验证签名。
与较低 API 级别相比的不同行为可能与在 Android P、here.
中实施的有关 BC Provider 的更改有关