Adobe AIRで開発されたアプリがあり、そのアプリをAndroid Studioで作り直したことがあります。
そのアプリはp12ファイルで署名されていました。Android Studioでキーストアファイルにp12ファイルを設定して、署名付きAPKを作成してみましたが、APKファイルには署名がついていませんでした。
そのアプリは2013年後半に公開されたので、p12ファイルもその頃作成されたようです。
(1)keytoolでfingerprint
keytoolでfingerprintを表示しようとしたら、次のエラーが。
$ keytool -list -v -keystore a.p12 -storetype pkcs12
キーストアのパスワードを入力してください:
keytoolエラー: java.security.cert.CertificateException: Unable to initialize, java.io.IOException: DerInputStream.getLength(): Redundant length bytes found
Code language: Bash (bash)
試しに JKS に変換しようとしましたが、同じエラーでした。
$ keytool -importkeystore -keystore b.jks -srckeystore a.p12 -srcstoretype PKCS12
キーストアのパスワードを入力してください:
keytoolエラー: java.security.cert.CertificateException: Unable to initialize, java.io.IOException: DerInputStream.getLength(): Redundant length bytes found
Code language: Bash (bash)
「DerInputStream.getLength(): Redundant length bytes found」で検索しても、約500件しかヒットしません。いくつかの記事を見ると、JDK 7で署名キーを作成し、JDK 8で署名しようとしたら、エラーになったという内容でした。
他にも調べたなかで、参考になりそうな記事をピックアップすると、
We had the same problem. We have found that JDK 1.8.0_112 doesn't have the bug that you're talking about. So we resolved the problem in this way:
Signing android app throws IOException: Redundant length bytes found
(Google翻訳)
同じ問題がありました。 JDK 1.8.0_112には、あなたが話しているバグがないことがわかりました。 そこで、この方法で問題を解決しました。
stack overflowの回答者は、JDK 1.8.0_112のkeytoolでJKSに変換できたそうです。
Cause
Imports using keytool fails with "Invalid keystore format" error
The source keystore was created using an IBM SDK, and the import is attempting to use OpenJDK which results in the 'Invalid keystore format' error.
(Google翻訳)
原因
ソースキーストアはIBM SDKを使用して作成されましたが、インポートはOpenJDKを使用しようとしているため、「無効なキーストア形式」エラーが発生します。
今回のエラーとは関係ないようですが、keytoolのJDKのバージョンによって、こういうエラーが起きる可能性があるようです。
Java 8 Update 121 (8u121)
Java 8リリースのハイライト
DERエンコーディング解析コードにチェックが追加されます
様々なエンコーディング・エラーを捕捉するために、DERエンコーディング解析コードにチェックが追加されます。また、構成済の不確定な長さのエンコーディングを含む署名により、解析中にIOExceptionが発生します。JDKデフォルト・プロバイダを使用して生成された署名はこの変更の影響を受けないことに注意してください。JDK-8168714 (非公開)
今回のエラーは「java.io.IOException: DerInputStream.getLength(): Redundant length bytes found」なので、これが原因かもしれません。
(2)いくつかのJDKでfingerprintを試す
オリジナルp12をいくつかのJDKで試してみました。
JDK | fingerprint表示 | JKSへ変換 |
Oracle JDK 1.7.0_80 | 成功 | 成功 |
openjdk-1.6.0_41 | エラー | エラー |
openjdk-1.7.0_201 | エラー | エラー |
openjdk-1.8.0_112 | 成功 | 成功 |
openjdk-1.8.0_212 | エラー | エラー |
変換したJKSを各JDKでfingerprint表示してみました。
JDK | fingerprint表示 | |
Oracle JDK 1.7.0_80 | 成功 | |
openjdk-1.6.0_41 | エラー | |
openjdk-1.7.0_201 | エラー | |
openjdk-1.8.0_112 | 成功 | |
openjdk-1.8.0_212 | エラー |
Oracle JDK 1.7.0_80とopenjdk-1.8.0_112で、fingerprint表示とJKS変換をすることができました。
openjdk-1.8.0_112は、Android Studio アーカイブのAndroid Studio 2.3同梱のJDKです。
(3)解決方法
(3−1)Android StudioのJDK location
Android StudioのFile>Project structure...>SDK location>JDK locationに、Android Studio 2.3(openjdk-1.8.0_112)のjreを設定しました。
Build>Generate Signed APKで、オリジナルp12を指定して、署名付きAPKを作成してみました。APKの署名とJKSの署名が同じことを確認しました。
Build>Generate Signed APKで、変換後のJKSを指定して、署名付きAPKを作成してみました。APKの署名とJKSの署名が同じことを確認しました。
APKの署名のためとはいえ、Android StudioのJDKを変更するのは、どうも心配です。筆者は、開発環境やビルドの仕組みについて理解していないので、なるべくデフォルト設定を変更しないようにしています。
(3−2)環境変数のPATHやJAVA_HOME
実際の運用では、リリースAPK作成やGoogle Play ConsoleへのAPKアップロードはJenkinsで処理したいところです。
そのためには、Android StudioでリリースAPK作成するのではなく、ターミナルで ./gradlew app:assembleRelease を実行して、リリースAPKを作成します。
ターミナルを開き、環境変数のPATHやJAVA_HOMEをAndroid Studio 2.3のjreに設定しました。
./gradlew app:assembleRelease でAPK作成してみました。
APKの署名とJKSの署名が同じことを確認しました。
しかし、この方法は、開発中のAndroid StudioのJDKと、リリースビルド用のJDKが違うことになるのが、気になります。
(3−3)jarsignerでAPK署名
そこで、Android Studio同梱のJDK(openjdk-1.8.0_112)を使ってリリースAPKを作成した後、Android Studio 2.3(openjdk-1.8.0_112)のkeytoolやjarsignerを使って、APKに署名する方法も調べました。
keytoolはjreの下、zipalignはAndroid SDKのbuild-toolsの下にあります。
#!/bin/bash
set -u
set -e
# 署名なしのリリースAPKを作成する
./gradlew app:assembleRelease
export storeFile=aaaa.p12
export storePassword=xxxx
export keyPassword=yyyy
export keyAlias=zzzz
# jarsigner, keytool, zipalign
export PATH=~/android-studio-2.3/jre/bin:$ANDROID_HOME/build-tools/25.0.3:$PATH
apk_dir=app/build/outputs/apk/release
build_apk=$(ls $apk_dir/*.apk)
signed_apk=$build_apk.signed.apk
aligned_apk=$signed_apk.aligned.apk
cp $build_apk $signed_apk
jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore $storeFile -storetype pkcs12 -storepass $storePassword -keypass $keyPassword $signed_apk $keyAlias
zipalign -v 4 $signed_apk $aligned_apk
keytool -list -printcert -jarfile $aligned_apk
Code language: Bash (bash)
最終的には、この方法を採用しました。
(3−4)Google Play App Signing
従来からのアプリ署名の方法は、開発者が、アプリ署名キーでAPKに署名して、Google Playにアップロードしていました。
Google Play App Signingは、アップロード署名キーとアプリ署名キーの2つを使います。
1.開発者は、アップロード署名キーでAPKに署名して、Google Playにアップロードします。
2.Google Playが、アプリ署名キーでAPKに署名して、ストアに公開します。
2のアプリ署名キーは、従来のアプリ署名キーです。あらかじめ、pepk.jarでエクスポートしてから、Google Playに登録しておきます。
オリジナルp12をpepk.jarでエクスポートできるか試しました。
JDK | キーストア | 結果 | |
1 | openjdk-1.8.0_112 | オリジナルp12 | エラーなし |
2 | openjdk-1.8.0_112 | 変換後JKS | エラーなし |
3 | openjdk-1.8.0_212 | オリジナルp12 | java.io.IOException: Invalid keystore format |
4 | openjdk-1.8.0_212 | 変換後JKS | java.io.IOException: DerInputStream.getLength() |
openjdk-1.8.0_112で、オリジナルp12をエクスポートすることができました。
しかし、openjdk-1.8.0_212 + オリジナルp12で「Invalid keystore format」になったことが気になります。
openjdk-1.8.0_112でも、オリジナルp12ではなくJKSを、pepk.jarでエクスポートしたほうがいいかもしれません。