こんにちは丸山@h13i32maruです
最近AndroidのCipher.javaで問題に遭遇して解決したので、そのメモです。
問題
AndroidでCipher.javaを使ってデータを暗号化するときに暗号化処理方式として[AES/CTR/PKCS5Padding]
を使用するとAndroid4.4とそれ以外のAndroid4.x/5.0で互換性がありません。Android4.2 → Android4.4やAndroid4.4 → Android5.0というOSアップデートを行うとデータを復号できなくなってしまいます。
これはAndroid4.4とそれ以外のAndroid4.x/5.0では[AES/CTR/PKCS5Padding]
という暗号化方式に互換性がないからです。実はJavaでは暗号化方式の指定以外にもSecurityProviderというものも指定できます。これはJavaでは暗号化処理の実装を複数もっており、そのうちのどれを使うかということを指定するものです。このSecurityProviderを指定しない場合、自動的にシステムがSecurityProviderを決定します。この時にAndroid4.4とそれ以外では使用されるSecurityProviderが異なります。よって暗号化されたデータをOSをまたぐと復号できなくなってしまいます。
SecurityProviderを指定せずに[AES/CTR/PKCS5Padding]
を使用する場合
- Android4.0: BC Provider
- Android4.1: BC Provider
- Android4.2: BC Provider
- Android4.3: 未確認
- Android4.4: AndroidOpenSSL Provider
- Android5.0: BC Provider
という結果になりました。つまり[AES/CTR/PKCS5Padding BC]
と[AES/CTR/PKCS5Padding AndroidOpenSSL]
が使われます。そしてこの2つの暗号化処理に互換性がなく、一方で暗号化したデータをもう一方で復号化することができませんでした。
何故互換性が無いかというとCTRというのはストリームモードで暗号化するという指定なのですが、この場合パディングは本来不要です。ですがPKCS5Paddingという方式を指定したことで、BCとAndroidOpenSSLでパディングの扱いに違いで出てしまい(一方はパディングを無視し、一方はパディングを行った)、結果として互換性がなくなってしまいました(と理解していますが間違っていたらごめんなさい)。
解決
まず[AES/CTR/PKCS5Padding BC]
で復号してみて、できなかった場合は[AES/CTR/NoPadding AndroidOpenSSL]
で復号するという二段階で乗り切ることにしました。
ここでなぜ[AES/CTR/PKCS5Padding AndroidOpenSSL]
ではなく[AES/CTR/NoPadding AndroidOpenSSL]
を使用したかというと、Android4.4以外では[AES/CTR/PKCS5Padding AndroidOpenSSL]
という処理方式を使えないからです。
もう1点ポイントが有り、かならず[AES/CTR/PKCS5Padding BC]
から試行するということです。何故かと言うと[AES/CTR/PKCS5Padding BC]
で暗号化したデータは[AES/CTR/NoPadding AndroidOpenSSL]
で復号できてしまうのです。しかし、複合されたデータは不正な値になっています(パッディングの残骸?)。つまり[AES/CTR/NoPadding AndroidOpenSSL]
を先に試行してしまうと、不正な値として復号されてしまいます。
それぞれの処理方式の関係をまとめると
[AES/CTR/PKCS5Padding AndroidOpenSSL]
のデータを[AES/CTR/PKCS5Padding BC]
で復号すると例外がでる[AES/CST/NoPadding AndroidOpenSSL]
のデータは[AES/CTR/PKCS5Padding BC]
で復号すると例外がでる[AES/CTR/PKCS5Padding AndroidOpenSSL]
のデータは[AES/CTR/NoPadding AndroidOpenSSL]
で復号することができる[AES/CTR/PKCS5Padding BC]
のデータは[AES/CST/NoPadding AndroidOpenSSL]
で復号すると例外はでないが、不正な値が取得されてしまう
暗号処理難しい。