Base64でデコード出来ない文字をデコードしようとするとどうなるか?
調べても出てこなかったので、メモ。
何かわかりやすいエラーが出るのかとおもいきや、エラーとわかりやすい挙動はしなかった…orz
答えは『実装による』っぽい。 実際に平文→base64.decode→sha256でhash→base64.encodeの処理をOpensslとJavaの両方で試したら挙動に違いがった。
例えばOpenSSLだとこうなる
$ echo "aaaa" | openssl base64 -d | openssl sha -sha256 -binary | openssl base64 # u6nbXovptodruQ0AGBFeI/x0G6a/IyXn/PiO/tdQxMc= $ echo "aaaa,,,," | openssl base64 -d | openssl sha -sha256 -binary | openssl base64 # 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
aaaa
の場合とaaaa,,,,
の場合で結果が異なる。
,
はbase64で使用していない文字なのでデコード出来ない。
そのため、,
が入るとエラーになって、その結果毎回同じ文字列が返ってきている(ように見える)。
試しにaaaaaaaa,
も試してみる
$ echo "aaaaaaa," | openssl base64 -d | openssl sha -sha256 -binary | openssl base64 # 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
やっぱり47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
になる。
なぜ47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
が返ってくるかはOpenSSLの中身を見ていないのでわからない。
Apache Commons Codecだとこうなる
package Base64Test; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.apache.commons.codec.binary.Base64; public class Base64TestExecutor { public static void main(String[] agrs) throws NoSuchAlgorithmException{ String target1 = "aaaa"; String target2 = "aaaa,,,,"; String result1 = testDigest(target1); String result2 = testDigest(target2); System.out.println("result1 : " + result1); System.out.println("result2 : " + result2); System.out.println("isEqual : " + result1.equals(result2)); } private static String testDigest(String targetString) throws NoSuchAlgorithmException { byte[] testResult = Base64.decodeBase64(targetString); MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] digestVal = md.digest(testResult); String encPassword = Base64.encodeBase64String(digestVal); return encPassword; } }
実行結果
run: result1 : u6nbXovptodruQ0AGBFeI/x0G6a/IyXn/PiO/tdQxMc= result2 : u6nbXovptodruQ0AGBFeI/x0G6a/IyXn/PiO/tdQxMc= isEqual : true ビルド成功(合計時間: 0秒)
こっちは中身を見てみた。
/** * <p> * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" * call is not necessary when decoding, but it doesn't hurt, either. * </p> * <p> * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, * garbage-out philosophy: it will not check the provided data for validity. * </p> * <p> * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ * </p> * * @param in * byte[] array of ascii data to base64 decode. * @param inPos * Position to start reading data from. * @param inAvail * Amount of bytes available from input for encoding. * @param context * the context to be used */ @Override void decode(final byte[] in, int inPos, final int inAvail, final Context context) { if (context.eof) { return; } if (inAvail < 0) { context.eof = true; } for (int i = 0; i < inAvail; i++) { final byte[] buffer = ensureBufferSize(decodeSize, context); final byte b = in[inPos++]; if (b == pad) { // We're done. context.eof = true; break; } else { if (b >= 0 && b < DECODE_TABLE.length) { final int result = DECODE_TABLE[b]; if (result >= 0) { context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK; context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result; if (context.modulus == 0) { buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS); buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); } } } } } // Two forms of EOF as far as base64 decoder is concerned: actual // EOF (-1) and first time '=' character is encountered in stream. // This approach makes the '=' padding characters completely optional. if (context.eof && context.modulus != 0) { final byte[] buffer = ensureBufferSize(decodeSize, context); // We have some spare bits remaining // Output all whole multiples of 8 bits and ignore the rest switch (context.modulus) { // case 0 : // impossible, as excluded above case 1 : // 6 bits - ignore entirely // TODO not currently tested; perhaps it is impossible? break; case 2 : // 12 bits = 8 + 4 context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); break; case 3 : // 18 bits = 8 + 8 + 2 context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); break; default: throw new IllegalStateException("Impossible modulus "+context.modulus); } } }
final int result = DECODE_TABLE[b];
の箇所でDECODE出来ない文字の場合は-1
が返される。
そのため、result >= 0
はfalse
になるので、その文字は読み飛ばされる。
その結果、例えばa
とa,,,
の結果はおなじになる。
こういうのは実際に試して動作確認して、中身のソースまで見てみないとわかんないですね!