AES128暗号化 - Java編 -
先日iPhone(というかObjective-C)でのAES128暗号化について触れましたが、今回はJava編です。未確認ですが、おそらくAndroidでも動きます。
/* * $ javac CryptAES.java * $ java CryptAES <secret_key> <iv> <message> */ import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.IvParameterSpec; class CryptAES { static final String cipher_type = "AES/CBC/PKCS5Padding"; public static void main(String[] args) { String key = args[0]; String iv = args[1]; String data = args[2]; byte[] enc = encode(key, iv, data.getBytes()); byte[] dec = decode(key, iv, enc); for (int i = 0; i < enc.length; i++) { System.out.printf("%02x", enc[i]); } System.out.println(); System.out.println(new String(dec)); } public static byte[] encode(String skey, String iv, byte[] data) { return process(Cipher.ENCRYPT_MODE, skey, iv, data); } public static byte[] decode(String skey, String iv, byte[] data) { return process(Cipher.DECRYPT_MODE, skey, iv, data); } private static byte[] process(int mode, String skey, String iv, byte[] data) { SecretKeySpec key = new SecretKeySpec(skey.getBytes(), "AES"); AlgorithmParameterSpec param = new IvParameterSpec(iv.getBytes()); try { Cipher cipher = Cipher.getInstance(cipher_type); cipher.init(mode, key, param); return cipher.doFinal(data); } catch (Exception e) { System.err.println(e.getMessage()); throw new RuntimeException(e); } } }
今回はivは指定のものを使うようにしてありますが、特に指定がなければ自動で生成されますし、そのivをあとから取り出すことももちろん可能です。
パディング方式に関しては外部ライブラリをいれない限りはPKCS7が使えません。ただし、デフォルトで使えるPKCS5は7と互換性が(ほぼ)あるため、特に問題が起きることはないでしょう(逆にPKCS7のライブラリがバグってるという話もあるので、無理にPKCS7にしようとすると本末転倒な結果になりかねません)。
それから、128bitの指定などは特にしていないのですが、secret keyやivの長さ(どちらも16バイト)から自動で判別しているのだと思います。secret keyの長さを変えれば自動で256bitになったりするのでしょうか(要検証)。
Javaは普段使ってないので、もしマズいところがあればご指摘頂けるとありがたいです。
iPhoneでAES128暗号化
iPhoneアプリ開発で暗号化を行う必要が出てきたので、試しに書いてみました。
暗号方式にはAES128を、Padding方式にはPKCS7を使っています。
ここで使っているCommonCryptoはiOS SDKについてくるものですが、mac上でも普通に使うことができます(iPhone Simulator用のライブラリを無理矢理使います)。
使い方としては、下のコードをコンパイルして、
$ ./a.out aaaabbbbccccdddd eeeeffffgggghhhh message
というようにすると"message"が暗号化され、さらに続けて復号化されます。
第一引数は鍵、第二引数はInitial Vectorです。どちらも16バイトである必要があります。
/* * gcc -std=c99 crypto.m -framework Foundation */ #import <Foundation/Foundation.h> #import <CommonCrypto/CommonCryptor.h> @interface NSData (AES) - (NSData *)AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv; - (NSData *)AES128EncryptWithKey:(NSString *)key iv:(NSString *)iv; - (NSData *)AES128DecryptWithKey:(NSString *)key iv:(NSString *)iv; @end @implementation NSData (AES) - (NSData *)AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv { char keyPtr[kCCKeySizeAES128 + 1]; memset(keyPtr, 0, sizeof(keyPtr)); [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]; char ivPtr[kCCBlockSizeAES128 + 1]; memset(ivPtr, 0, sizeof(ivPtr)); [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding]; NSUInteger dataLength = [self length]; size_t bufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_t numBytesCrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(operation, kCCAlgorithmAES128, kCCOptionPKCS7Padding, keyPtr, kCCBlockSizeAES128, ivPtr, [self bytes], dataLength, buffer, bufferSize, &numBytesCrypted); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted]; } free(buffer); return nil; } - (NSData *)AES128EncryptWithKey:(NSString *)key iv:(NSString *)iv { return [self AES128Operation:kCCEncrypt key:key iv:iv]; } - (NSData *)AES128DecryptWithKey:(NSString *)key iv:(NSString *)iv { return [self AES128Operation:kCCDecrypt key:key iv:iv]; } @end int main(int argc, char const* argv[]) { NSAutoreleasePool* pool; pool = [[NSAutoreleasePool alloc] init]; NSString *key = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding]; NSString *iv = [NSString stringWithCString:argv[2] encoding:NSUTF8StringEncoding]; NSString *data_str = [NSString stringWithCString:argv[3] encoding:NSUTF8StringEncoding]; NSData *data = [data_str dataUsingEncoding:NSUTF8StringEncoding]; NSData *en_data = [data AES128EncryptWithKey:key iv:iv]; NSData *de_data = [en_data AES128DecryptWithKey:key iv:iv]; NSString *de_str = [[[NSString alloc] initWithData:de_data encoding:NSUTF8StringEncoding] autorelease]; NSLog(@"%@", en_data); NSLog(@"%@", de_str); [pool drain]; return 0; }
暗号の話は難しいですね。勉強が必要です。
補足(2011/10/18):
CCCryptはデフォルトでCBC(cipher block chaining)を使うようになっています。ECB(electronic codebook)を指定することもできますが、使う理由は特にないでしょう。
libeventでHTTPサーバを試す
libeventに含まれるevhttpを使って、簡単なhttpサーバを作ってみました。
404を返す
まずは404 not foundを返すコードを書いてみます。ただし、GET以外のメソッドの場合はBad Requestを返すようにします。
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <event.h> #include <evhttp.h> #define HTTPD_ADDR "0.0.0.0" #define HTTPD_PORT 8080 void req_handler(struct evhttp_request *r, void *arg) { if (r->type != EVHTTP_REQ_GET) evhttp_send_error(r, HTTP_BADREQUEST, "Available GET only"); else evhttp_send_error(r, HTTP_NOTFOUND, "Not Found"); } int main(int argc, const char* argv[]) { struct event_base *ev_base; struct evhttp *httpd; ev_base = event_base_new(); httpd = evhttp_new(ev_base); if (evhttp_bind_socket(httpd, HTTPD_ADDR, HTTPD_PORT) < 0) { perror("evhttp_bind_socket()"); exit(EXIT_FAILURE); } evhttp_set_gencb(httpd, req_handler, NULL); event_base_dispatch(ev_base); evhttp_free(httpd); event_base_free(ev_base); return 0; }
コンパイル・実行して、ブラウザでlocalhost:8080にアクセスすると"Not Found"と表示されます。
plain textを返す
今度はtext/plainで"Hello World"と返すようにしてみます。main関数は上のものと同じなので省略します。
void req_handler(struct evhttp_request *r, void *arg) { struct evbuffer *evbuf; char message[] = "Hello World"; int message_length = strlen(message); char content_length[8]; evbuf = evbuffer_new(); if (evbuf == NULL) { evhttp_send_error(r, HTTP_SERVUNAVAIL, "Failed to create buffer"); return; } snprintf(content_length, 7, "%d", message_length); evhttp_add_header(r->output_headers, "Content-Type", "text/plain"); evhttp_add_header(r->output_headers, "Content-Length", content_length); evbuffer_add(evbuf, message, message_length); evhttp_send_reply(r, HTTP_OK, "", evbuf); evbuffer_free(evbuf); }
少し複雑になってきましたが、基本的にはheaderを用意して、struct evbufferにbodyを書き込んで送る、というだけです。
もう少しマシなhttpサーバ
エラー対策などはかなり無視しますが、それなりに動くhttpサーバを書いてみます。
#define DOCUMENT_ROOT "." static const char *get_mime_type(const char *filepath) { const char *filetype = strrchr(filepath, '.'); if (strcasecmp(filetype, ".html") == 0 || strcasecmp(filetype, ".htm") == 0) return "text/html"; else if (strcasecmp(filetype, ".js") == 0) return "text/javascript"; else if (strcasecmp(filetype, ".css") == 0) return "text/css"; else if (strcasecmp(filetype, ".txt") == 0) return "text/plain"; else if (strcasecmp(filetype, ".ico") == 0) return "image/x-icon"; else if (strcasecmp(filetype, ".png") == 0) return "image/png"; else if (strcasecmp(filetype, ".gif") == 0) return "image/gif"; else if (strcasecmp(filetype, ".jpeg") == 0 || strcasecmp(filetype, ".jpg") == 0) return "image/jpeg"; else if (strcasecmp(filetype, ".pdf") == 0) return "application/pdf"; return "application/octet-stream"; } static const char *get_content_from_file(const char *req_path, char **content, int *content_length) { struct stat sb; static char filepath[1024]; FILE *fp; int rsize; *content = NULL; sprintf(filepath, "%s%s", DOCUMENT_ROOT, req_path); if (stat(filepath, &sb) < 0) return filepath; if (S_ISDIR(sb.st_mode)) { if (strcmp(req_path, "/") == 0) sprintf(filepath, "%s%sindex.html", DOCUMENT_ROOT, req_path); else sprintf(filepath, "%s%s/index.html", DOCUMENT_ROOT, req_path); if (stat(filepath, &sb) < 0) return filepath; } *content_length = (int)sb.st_size; fp = fopen(filepath, "rb"); if (fp) { *content = (char *)malloc(*content_length); rsize = fread(*content, 1, *content_length, fp); fclose(fp); if (rsize != *content_length) { free(*content); *content = NULL; } } return filepath; } void req_handler(struct evhttp_request *r, void *arg) { struct evbuffer *evbuf; const char *req_path, *res_path; char *content; int content_length; char content_length_str[12]; if (r->type != EVHTTP_REQ_GET) { evhttp_send_error(r, HTTP_BADREQUEST, "Available GET only"); return; } req_path = evhttp_request_uri(r); res_path = get_content_from_file(req_path, &content, &content_length); if (content == NULL) { evhttp_send_error(r, HTTP_NOTFOUND, "Not Found"); return; } sprintf(content_length_str, "%d", content_length); evhttp_add_header(r->output_headers, "Content-Type", get_mime_type(res_path)); evhttp_add_header(r->output_headers, "Content-Length", content_length_str); evbuf = evbuffer_new(); evbuffer_add(evbuf, content, content_length); evhttp_send_reply(r, HTTP_OK, "", evbuf); evbuffer_free(evbuf); }
mime typeが全然足りないとか、mime typeの判別はハッシュ使ったほうがいいとかは気にしないでください。とりあえずそれなりにまともな動作をします。
まとめ
evhttpの基本的な使い方は分かったので、あとは細かいことをやっていくだけですね。わざわざhttpサーバを書く機会も滅多にないと思いますが、楽しかったので満足です。