blog

Java script:詳細なBase64エンコードとデコード

Base64 エンコーディングは RFC2045 で定義されており、次のように定義されています。Base64コンテンツ配信エンコーディングは、任意の8ビットバイト列を人間が容易に認識できない形式として...

Jun 22, 2025 · 24 min. read
シェア

Base64は最もよく使われるエンコーディングの一つで、例えば開発でパラメータを渡したり、モダンブラウザの<img />タグでBase64文字列から直接イメージをレンダリングしたり、電子メールなどで使われます。Base64エンコーディングはRFC 2045で定義されており、以下のように定義されています。Base64 content delivery encoding is designed to describe an arbitrary sequence of 8-bit bytes into a form that is readily not recognized by humans.

文字エンコーディングの基本

当初、コンピュータがサポートしていたのはASCIIコードのみで、文字は下位7ビットのみを使用したバイトで表現され、***ビットは0でした。 その後、多くの地域の言語をサポートするため、主要な組織やITベンダーはASCIIコードの欠点を補う独自のコード体系を考案し始め、GB2312エンコーディングGB2312、GBK、Big5などです。しかし、これらのエンコーディングは、地域や一部の言語のみを対象としており、すべての言語を表現するものではありません。さらに、これらの異なるエンコーディングの間には関連性がなく、これらの間の変換はテーブルをルックアップすることで実現する必要があります。

コンピュータの情報処理・交換機能を向上させ、世界各国の文字をコンピュータで処理できるようにするため、1984年からISO組織が新たな規格の研究開発に着手。

べいこくきかくきょうかい

ANSIは特定のエンコーディングのことではありません。例えば、簡体字ウィンドウズではGB2312、繁体字ウィンドウズではBig5、日本語オペレーティングシステムではJISを意味します。つまり、新規テキストファイルを作成し、ANSIエンコーディングで保存した場合、このファイルのエンコーディングはローカルエンコーディングであることがわかります。

ユニコード

Unicodeエンコーディングは文字テーブルと一対一にマッピングされます。例えば、56DEは漢字'回'を表し、このマッピング関係は固定です。一般的に言えば、Unicodeエンコーディングは文字テーブルの座標であり、56DEを通して漢字'回'を見つけることができます。 Unicodeエンコーディングの実装にはUTF8、UTF16、UTF32などがあります。

ユニコードそのものは、各文字の数値、つまり文字と自然数との対応関係を定義しています。一方、UTF-8やUTF-16、あるいはUTF-32は、バイトストリームにおける文字の区切り方を定義しており、これはコンピュータ分野の概念です。

上の図から、UTF-8エンコーディングは可変長エンコーディング方式であり、1~6バイトで構成され、Unicodeエンコーディング値の間隔で判断でき、UTF8文字を構成する各バイトは規則的であることがわかります。この記事では、UTF8とUTF16の2つのエンコーディングについてのみ説明します。

UTF16

UTF16エンコーディングは、格納するために固定2バイトを使用しています。UTF16エンコーディングは、Unicodeを実装する最も直接的な方法であり、通常、新しいテキストファイルをWindows上でUnicodeエンコーディングとして保存した後、実際には、UTF16エンコーディングとして保存されます。私は新しいテキストファイルを作成し、それをテストするためにUnicodeとして保存し、ファイルは、私がそれを開くためにEditplusを使用した後、16進モードビューに切り替えると、図のように、中国語の文字'回'を入力するだけです:

最初の2バイトFF FEはファイルヘッダで、これがUTF16エンコードされたファイルであることを示し、DE 56はUTF16エンコードされた16進数に「戻る」ことを示します。よく使われるJavaScript言語は、内部的にUTF16エンコードされており、ビッグエンディアンの順序で格納されています:

<script type="text/javascript">  
console.group('Test Unicode: ');  
console.log((' .charCodeAt(0)).toString(16).toUpperCase());  
</script>  

Editplusの表示とは明らかに順序が逆になっています

UTF8

UTF8は、1〜6バイトの可変長エンコーディングの使用ですが、他のケースは本当にまれであるため、それは、通常、唯一のシングルバイトまたは3バイトの実装として見られている。UTF8のエンコーディングは、複数のバイトの組み合わせを介して、これがないバイト順であり、各バイトは非常に規則的であるUTF8に対処するためのコンピュータのメカニズムであることを示すために、上の図を参照してください、ここで詳細に説明されることはありません。

UTF16UTF8による相互変換

UTF16 から UTF8

UTF16とUTF8の間の変換は、上記の変換テーブルを使用して、Unicodeコードが配置されている間隔を判断することによって達成することができ、文字を取得することができますいくつかのバイトで構成され、次に達成するためにシフトされます。変換の例は、中国語の文字'回'です。

漢字「回」のUnicodeコードは0x56DEで、U+00000800からU+0000FFFFの間なので、3バイトで表されることはすでに知られています。

そのため、0x56DEの2バイト値を3バイト値に変更する必要があります。 上図のxの部分が、0x56DEの各ビットに対応するバイトであることに注意してください。 xの数を数えると、ちょうど16ビットであることがわかります。

思考の転換

0x56DEから4ビットを取り出してロービットに入れ、2進数の1110と組み合わせると、これが***バイト目になります。0x56DEの残りのバイトから6ビットをロービットに入れ、2進数の10と組み合わせます。3バイト目も同様です。

コードの実装

よりよく理解するために、以下のコードは漢字「回」の変換を実装しているだけです:

<script type="text/javascript">  
/**  
* 変換比較表  
* U+00000000 – U+0000007F   0xxxxxxx  
* U+00000080 – U+000007FF   110xxxxx 10xxxxxx  
* U+00000800 – U+0000FFFF   1110xxxx 10xxxxxx 10xxxxxx  
* U+00010000 – U+001FFFFF   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  
* U+00200000 – U+03FFFFFF   111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
* U+04000000 – U+7FFFFFFF   1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
*/ 
/*  
* '文字エンコーディングが同じなので、結果も同じである。+00000800 – U+0000FFFF文字エンコードは同じなので、結果は同じだ。  
* U+00000800 – U+0000FFFF   1110xxxx 10xxxxxx 10xxxxxx  
*/ 
var ucode = 0x56DE;  
// 1110xxxx  
var byte1 = 0xE0 | ((ucode >> 12) & 0x0F);  
// 10xxxxxx  
var byte2 = 0x80 | ((ucode >> 6) & 0x3F);  
// 10xxxxxx  
var byte3 = 0x80 | (ucode & 0x3F);  
var utf8 = String.fromCharCode(byte1)  
        + String.fromCharCode(byte2)  
        + String.fromCharCode(byte3);  
 
console.group('Test UTF16ToUTF8: ');  
console.log(utf8);  
console.groupEnd();  
</script>  

JavaScriptはUTF8文字を表示する方法を知らないので、出力はちんぷんかんぷんです。出力を不適切に変換することに何の意味があるのかと言われるかもしれませんが、変換の目的はしばしばトランスポートやAPIのニーズにも使われることを知っておくべきです。

UTF8からUTF16

これはUTF16からUTF8への逆変換で、変換テーブルに従って変換する必要があります。図に示すように、変換テーブルに従ってダブルバイトに変換し、すべてのxを保持する必要があります。

コードは以下の通り:

<script type="text/javascript">  
/**  
* 変換比較表  
* U+00000000 – U+0000007F   0xxxxxxx  
* U+00000080 – U+000007FF   110xxxxx 10xxxxxx  
* U+00000800 – U+0000FFFF   1110xxxx 10xxxxxx 10xxxxxx  
* U+00010000 – U+001FFFFF   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  
* U+00200000 – U+03FFFFFF   111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
* U+04000000 – U+7FFFFFFF   1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
*/ 
/*  
* '文字エンコーディングが同じなので、結果も同じである。+00000800 – U+0000FFFF文字エンコードは同じなので、結果は同じだ。  
* U+00000800 – U+0000FFFF   1110xxxx 10xxxxxx 10xxxxxx  
*/ 
var ucode = 0x56DE;  
// 1110xxxx  
var byte1 = 0xE0 | ((ucode >> 12) & 0x0F);  
// 10xxxxxx  
var byte2 = 0x80 | ((ucode >> 6) & 0x3F);  
// 10xxxxxx  
var byte3 = 0x80 | (ucode & 0x3F);  
var utf8 = String.fromCharCode(byte1)  
        + String.fromCharCode(byte2)  
        + String.fromCharCode(byte3);  
 
console.group('Test UTF16ToUTF8: ');  
console.log(utf8);  
console.groupEnd();  
/** ------------------------------------------------------------------------------------*/ 
// 文字エンコードは同じなので、結果は同じだ。  
var c1 = utf8.charCodeAt(0);  
var c2 = utf8.charCodeAt(1);  
var c3 = utf8.charCodeAt(2);  
/*  
* 特定のビットを判定して変換する必要があるが、ここでは3バイトであることがわかっているので、判定は無視され、代わりに16ビットを構成するすべてのxが得られるだけだ。  
* U+00000800 – U+0000FFFF   1110xxxx 10xxxxxx 10xxxxxx  
*/ 
//  ***最初のバイトの上位4ビットは、2番目のバイトの上位4ビットと組み合わされて1バイトになる。  
var b1 = (c1 << 4) | ((c2 >> 2) & 0x0F);  
// 同様に、2バイト目と3バイト目の組み合わせである  
var b2 = ((c2 & 0x03) << 6) | (c3 & 0x3F);  
// b1とb2を16ビットの  
var ucode = ((b1 & 0x00FF) << 8) | b2;  
console.group('Test UTF8ToUTF16: ');  
console.log(ucode.toString(16).toUpperCase(), String.fromCharCode(ucode));  
console.groupEnd();  
</script> 

変換ルールを知っていれば、簡単に実行できます。

Base64

Base64エンコーディングでは、3つの8ビット・バイトを4つの6ビット・バイトに変換した後、6ビットの前に2つのゼロを加えて1バイト8ビットにします。2の6乗は64なので、6ビットごとにある印刷可能な文字に対応する単位となります。元データが3の整数倍でない場合、***残り2つの入力データがあれば、1"=を加算した後にエンコードし、***残り1つの入力データがあれば、2"=を加算した後にエンコードします。

トランスコード比較表

トランスコード・テーブルで対応する印字可能文字を検索し、6セルごとに2つのゼロを補完して形成されるバイトは、0から63の間に位置します。はパディングに使用されます。下図にトランスコード・テーブルを示します。

.

#p#

Base64

復号化は、エンコードの逆のプロセスであり、最初に補数"="記号の数の背面を見て、最大2つだけ"="記号。補数0の背面を削除し、8ビット展開によると、復元することができますので、"="は、2つの0を構成するために等価です。

Base64エンコードとデコードのJavaScript実装

この記事の例では、Base64エンコーディングの基礎としてUTF8エンコーディングされた文字列を使用しています。JavaScriptは内部的にUnicodeエンコーディングを使用しているため、変換処理が必要です:

<script type="text/javascript">  
/**  
* UTF16とUTF8変換テーブル  
* U+00000000 – U+0000007F   0xxxxxxx  
* U+00000080 – U+000007FF   110xxxxx 10xxxxxx  
* U+00000800 – U+0000FFFF   1110xxxx 10xxxxxx 10xxxxxx  
* U+00010000 – U+001FFFFF   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  
* U+00200000 – U+03FFFFFF   111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
* U+04000000 – U+7FFFFFFF   1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
*/ 
var Base64 = {  
    // トランスコード表  
    table : [  
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',  
            'I', 'J', 'K', 'L', 'M', 'N', 'O' ,'P',  
            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',  
            'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',  
            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',  
            'o', 'p', 'q', 'r', 's', 't', 'u', 'v',  
            'w', 'x', 'y', 'z', '0', '1', '2', '3',  
            '4', '5', '6', '7', '8', '9', '+', '/' 
    ],  
    UTF16ToUTF8 : function(str) {  
        var res = [], len = str.length;  
        for (var i = 0; i < len; i++) {  
            var code = str.charCodeAt(i);  
            if (code > 0x0000 && code <= 0x007F) {  
                // 0x0000という1バイトはヌル・バイトなので、ここでは考慮しない。  
                // U+00000000 – U+0000007F  0xxxxxxx  
                res.push(str.charAt(i));  
            } else if (code >= 0x0080 && code <= 0x07FF) {  
                // ダブルバイト  
                // U+00000080 – U+000007FF  110xxxxx 10xxxxxx  
                // 110xxxxx  
                var byte1 = 0xC0 | ((code >> 6) & 0x1F);  
                // 10xxxxxx  
                var byte2 = 0x80 | (code & 0x3F);  
                res.push(  
                    String.fromCharCode(byte1),   
                    String.fromCharCode(byte2)  
                );  
            } else if (code >= 0x0800 && code <= 0xFFFF) {  
                // 3バイト  
                // U+00000800 – U+0000FFFF  1110xxxx 10xxxxxx 10xxxxxx  
                // 1110xxxx  
                var byte1 = 0xE0 | ((code >> 12) & 0x0F);  
                // 10xxxxxx  
                var byte2 = 0x80 | ((code >> 6) & 0x3F);  
                // 10xxxxxx  
                var byte3 = 0x80 | (code & 0x3F);  
                res.push(  
                    String.fromCharCode(byte1),   
                    String.fromCharCode(byte2),   
                    String.fromCharCode(byte3)  
                );  
            } else if (code >= 0x00010000 && code <= 0x001FFFFF) {  
                // 4バイト  
                // U+00010000 – U+001FFFFF  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  
            } else if (code >= 0x00200000 && code <= 0x03FFFFFF) {  
                // 5バイトだ。  
                // U+00200000 – U+03FFFFFF  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
            } else /** if (code >= 0x04000000 && code <= 0x7FFFFFFF)*/ {  
                // 6バイト  
                // U+04000000 – U+7FFFFFFF  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
            }  
        }  
 
        return res.join('');  
    },  
    UTF8ToUTF16 : function(str) {  
        var res = [], len = str.length;  
        var i = 0;  
        for (var i = 0; i < len; i++) {  
            var code = str.charCodeAt(i);  
            //  ***バイトは  
            if (((code >> 7) & 0xFF) == 0x0) {  
                // シングルバイト  
                // 0xxxxxxx  
                res.push(str.charAt(i));  
            } else if (((code >> 5) & 0xFF) == 0x6) {  
                // ダブルバイト  
                // 110xxxxx 10xxxxxx  
                var code2 = str.charCodeAt(++i);  
                var byte1 = (code & 0x1F) << 6;  
                var byte2 = code2 & 0x3F;  
                var utf16 = byte1 | byte2;  
                res.push(Sting.fromCharCode(utf16));  
            } else if (((code >> 4) & 0xFF) == 0xE) {  
                // 3バイト  
                // 1110xxxx 10xxxxxx 10xxxxxx  
                var code2 = str.charCodeAt(++i);  
                var code3 = str.charCodeAt(++i);  
                var byte1 = (code << 4) | ((code2 >> 2) & 0x0F);  
                var byte2 = ((code2 & 0x03) << 6) | (code3 & 0x3F);  
                utf16 = ((byte1 & 0x00FF) << 8) | byte2  
                res.push(String.fromCharCode(utf16));  
            } else if (((code >> 3) & 0xFF) == 0x1E) {  
                // 4バイト  
                // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  
            } else if (((code >> 2) & 0xFF) == 0x3E) {  
                // 5バイトだ。  
                // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
            } else /** if (((code >> 1) & 0xFF) == 0x7E)*/ {  
                // 6バイト  
                // 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
            }  
        }  
 
        return res.join('');  
    },  
    encode : function(str) {  
        if (!str) {  
            return '';  
        }  
        var utf8    = this.UTF16ToUTF8(str); // UTF8に変換する  
        var i = 0; // インデックスを繰り返し処理する  
        var len = utf8.length;  
        var res = [];  
        while (i < len) {  
            var c1 = utf8.charCodeAt(i++) & 0xFF;  
            res.push(this.table[c1 >> 2]);  
            // 文字エンコードは同じなので、結果は同じだ。=  
            if (i == len) {  
                res.push(this.table[(c1 & 0x3) << 4]);  
                res.push('==');  
                break;  
            }  
            var c2 = utf8.charCodeAt(i++);  
            // 文字エンコードは同じなので、結果は同じだ。=  
            if (i == len) {  
                res.push(this.table[((c1 & 0x3) << 4) | ((c2 >> 4) & 0x0F)]);  
                res.push(this.table[(c2 & 0x0F) << 2]);  
                res.push('=');  
                break;  
            }  
            var c3 = utf8.charCodeAt(i++);  
            res.push(this.table[((c1 & 0x3) << 4) | ((c2 >> 4) & 0x0F)]);  
            res.push(this.table[((c2 & 0x0F) << 2) | ((c3 & 0xC0) >> 6)]);  
            res.push(this.table[c3 & 0x3F]);  
        }  
 
        return res.join('');  
    },  
    decode : function(str) {  
        if (!str) {  
            return '';  
        }  
 
        var len = str.length;  
        var i   = 0;  
        var res = [];  
 
        while (i < len) {  
            code1 = this.table.indexOf(str.charAt(i++));  
            code2 = this.table.indexOf(str.charAt(i++));  
            code3 = this.table.indexOf(str.charAt(i++));  
            code4 = this.table.indexOf(str.charAt(i++));  
 
            c1 = (code1 << 2) | (code2 >> 4);  
            c2 = ((code2 & 0xF) << 4) | (code3 >> 2);  
            c3 = ((code3 & 0x3) << 6) | code4;  
 
            res.push(String.fromCharCode(c1));  
 
            if (code3 != 64) {  
                res.push(String.fromCharCode(c2));  
            }  
            if (code4 != 64) {  
                res.push(String.fromCharCode(c3));  
            }  
 
        }  
 
        return this.UTF8ToUTF16(res.join(''));  
    }  
};  
 
console.group('Test Base64: ');  
var b64 = Base64.encode('Hello, osJapan!また春が来た!~');  
console.log(b64);  
console.log(Base64.decode(b64));  
console.groupEnd();  
</script>  

言うまでもなく、JavaScriptで実装するのは本当に面倒です。同じ文字列をPHPでエンコードした結果を見てみましょう:

文字エンコーディングが同じなので、結果も同じです。

Read next

7つのステップでAngular.JSの初心者からエキスパートへ (3): データバインディングとAJAX

これは "AngularJS - From Rookie to Expert in 7 Steps "シリーズの一部です。\nでは、アプリケーションのビルドを開始する方法を示しています。scopeと$scope関数について説明します。\nこのチュートリアルのシリーズ全体を通して、NPRの広いアプリケーションが開発されます。

Jun 22, 2025 · 8 min read