kanetaiの二次記憶装置

プログラミングに関するやってみた、調べた系のものをQitaに移して、それ以外をはてブでやる運用にしようと思います。http://qiita.com/kanetai

Hash(MDx, SHA-xxx)

MD5を使う機会があったので、色々な言語での求め方を調べてみた。

Uinux系

md5, md5sumが使えれば↓でおk

md5sum <file>
md5 <file>
md5 -s <text>

brewでインストールするなら、

brew install md5sha1sum

shasumもついてくる。

shasum [OPTION]... [FILE]...
With no FILE, or when FILE is -, read standard input.
  -a, --algorithm   1 (default), 224, 256, 384, 512, 512224, 512256

Objective-C

長さCC_MD5_DIGEST_LENGTHのバッファmdを用意して、

unsigned char *CC_MD5(const void *data, CC_LONG len, unsigned char *md)

に渡すだけ。

巨大なバイト列を分割して処理する場合は、

int CC_MD5_Init(CC_MD5_CTX *c);
int CC_MD5_Update(CC_MD5_CTX *c, const void *data, CC_LONG len);
int CC_MD5_Final(unsigned char *md, CC_MD5_CTX *c);

を使う。

他のハッシュアルゴリズムを使う場合はMD5の部分を変える。 用意されているアルゴリズムは、

MD2, MD4, MD5, SHA1, SHA224, SHA256, SHA384, SHA512

#import <CommonCrypto/CommonDigest.h>

NSString* convertDigest(unsigned char *digest, int digestLength) {
    NSMutableString *ret = [NSMutableString stringWithCapacity:digestLength * 2];
    for(int i = 0; i < digestLength; ++i)
        [ret appendFormat:@"%02x", digest[i]];
    return ret;
}

NSString* MD5Hash(const void *data, NSUInteger len) {
    unsigned char md[CC_MD5_DIGEST_LENGTH];
    CC_MD5(data, (CC_LONG)len, md);
    return convertDigest(md, CC_MD5_DIGEST_LENGTH);
}

NSString* MD5HashArray(const void *datas[], NSArray *lenArray) {
    unsigned char md[CC_MD5_DIGEST_LENGTH];
    CC_MD5_CTX ctx;
    CC_MD5_Init(&ctx);
    for (int i = 0; i < lenArray.count; ++i)
        CC_MD5_Update(&ctx, datas[i], (CC_LONG)[((NSNumber*)(lenArray[i])) unsignedIntegerValue]);
    CC_MD5_Final(md, &ctx);
    return convertDigest(md, CC_MD5_DIGEST_LENGTH);
}

#pragma mark - MD5 test
static const NSString * const str = @"D364D54F-597E-4375-B329-2DE3B2E3539D";
static const char * const strs[] = {
    "D364D54F-", "597E-4375-", "B329-2DE3B2E3539D"
};
static const int N = sizeof(strs)/sizeof(strs[0]);
void testMD5(void) {
    NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
    NSLog(@"%@\n%@\n", MD5Hash([str UTF8String], str.length), MD5Hash(data.bytes, data.length));
    
    NSMutableArray *lenArray = [NSMutableArray arrayWithCapacity:N*2];
    for (int i = 0; i < N; ++i) [lenArray addObject:@(strlen(strs[i]))];
    NSLog(@"%@", MD5HashArray((const void**)strs, lenArray));
}

Perl

perldoc

 # 関数形式
 use Digest::MD5  qw(md5 md5_hex md5_base64);

 $digest = md5($data);
 $digest = md5_hex($data);
 $digest = md5_base64($data);

 # OO 形式
 use Digest::MD5;

 $ctx = Digest::MD5->new;

 $ctx->add($data);
 $ctx->addfile(*FILE);

 $digest = $ctx->digest;
 $digest = $ctx->hexdigest;
 $digest = $ctx->b64digest;

OO形式を使えば、ファイル指定や分割して渡すこともできる。

#ファイル指定
open my $fh, '<', $filepath or die "Cannot open '$filepath': $!";
my $md5 = Digest::MD5->new->addfile($fh)->hexdigest;
#ブロック分割
my $md5 = Digest::MD5->new;
while (<FILE>) {
    $md5->add($_);
}
my $md5str = $md5->b64digest;

他のアルゴリズムだいたい同じ感じ、 SHAxxxの場合、new でxxxを指定できる。 CPAN

use Digest::SHA
my $alg = 256;
my $sha = Digest::SHA->new($alg)->addfile($fh)->hexdigest;

Java

java.security.MessageDigestを使う。 getInstance(alg)アルゴリズムを指定して、インスタンスを作る。 digest(byte[])でハッシュ結果がbyte配列で得られる。 ブロック分割する場合は、update()を使う。

MD2, MD5, SHA, SHA-256, SHA-384, SHA-512が使えるみたい。

import java.io.*;
import java.security.*;
public class HashTest {
    static public String hashWithAlgorihtm(String algorithm, byte[] data) throws NoSuchAlgorithmException {
        return convertDigest(MessageDigest.getInstance(algorithm).digest(data));
    }
    static public String hashWithAlgorithm(String algorithm, InputStream in) throws NoSuchAlgorithmException, IOException {
        MessageDigest md = MessageDigest.getInstance(algorithm);
        byte[] buff = new byte[1024];
        for (int len = 0;(len = in.read(buff, 0, buff.length)) >= 0; md.update(buff, 0, len));
        return convertDigest(md.digest());
    }
    static private String convertDigest(byte[] digest) {
        StringBuilder sb = new StringBuilder(digest.length * 2);
        for (byte b : digest) {
            String s = Integer.toHexString(b & 0xff);
            if (s.length() == 1) sb.append("0");
            sb.append(s);
        }
        return sb.toString();
    }
    static final String test = "D364D54F-597E-4375-B329-2DE3B2E3539D", fname = "test.txt";
    static void test(String algorithm) {
        try (FileInputStream in = new FileInputStream(fname)) {
            System.out.println(hashWithAlgorihtm(algorithm, test.getBytes()));
            System.out.println(hashWithAlgorithm(algorithm, in));
        } catch (IOException | NoSuchAlgorithmException e) { e.printStackTrace(); } 
    }
    public static void main(String[] args) {
        try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(fname)))) {
            pw.print(test);
        } catch (IOException e) { e.printStackTrace(); }
        
        test("md5");
    }
}

C/C++

詳しく調べてないですが、Open SSLとかCrypto++を使えば良さそうです。

現バージョンのboostにはなかった... ※Boost.Uuidにsha1クラスがあるので、sha1だけなら作れるみたい。 http://nukesaq88.hatenablog.com/entry/2013/04/19/183424