2016年2月26日金曜日

Go言語とGoogleAppEngineに触れる / Go言語

mainpicture

Go言語というものを仕事の関係上触ることになったので、そのキャッチアップした内容をまとめていきます。また、Google App Engine上での開発案件であったため、そこでハマったことなども合わせて書いていきます。

Go言語とGoogle App Engineに触れる

まずは、Go言語の雑感を簡単に。

  • 何の言語に似ているのかと言われれば、swiftに近いイメージ
  • でもかなりシンプル
  • 「go」「defer」「interface」「interface{}」「channel」が大切

幸いにして、Go言語にはとても手厚いチュートリアルが用意されています。

Go Tour

読みながら一通りやればある程度書けるようになります。素晴らしいですね!プログラミング言語の学習問わず、全ての学習をする上で「知ること」「聞くこと」「使うこと」という3ステップが大切です。Go言語の概要を知り、Go Tourを読み、動かしながら実際に書いていくようにしましょう!

ということで、実際にある程度やってみたので、その記録と感想を書きます。



Go Tour

だらだらと内容を併記しても内容が重複するので、気づいた箇所だけピックアップして記載していきます。

package

import (
    "fmt"
    "math/rand"
)

とかで記載します。ただ、そのコード内で利用していない場合はコンパイル時にエラーになります。ここが最初かなりしんどかった箇所です。

例えばデバッグ中にログを書き出したい時

import (
    "fmt"
)

func main() {
    v := 1
    v = v + 1
    fmt.Printf("The value is %v", v)    
}

これは大丈夫なのですが、

import (
    "fmt"
)

func main() {
    v := 1
    v = v + 1
}

これでエラーになります。毎回importまで戻って足したり消したりするのがめんどくさいです。 確かに不要なimportがなくなるのでコードは短くすっきりするし、依存関係も読みやすいのでありがたいところではあるのですが一長一短かと。

また、暗黙的な型宣言として

// これを
var v int = 1
// こう書ける
v := 1

といった記法があるのですが、良い点は型を意識しないでもある程度書けること、悪い点は何の型が入ってきているのかが分かりづらいことというのがあります。

例えば、

import (
    "google.golang.org/appengine"
)

func index(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    // 処理
}

これのcが何を表しているのかが分からないのです。何のパッケージをインストールしたらいいのやらという感じです。この段階だとコンパイルが通りますが、

import (
    "google.golang.org/appengine"
)

func index(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    sub(c)
}

func sub(c ????) {
    // 処理
}

この時点で結局importを書く必要があり、少し残念な気持ちになりました。

"golang.org/x/net/context"をimportすればcontextが解決され、???の箇所はcontext.Contextとすれば良いです。

function

頭文字を大文字にするとPublicの意味になります。

Go言語は高階関数を扱えるようになっていて、funcを引数に渡したり、closerを作ったりすることができます。javascriptやhaskellでも同様のことができますね。

実際に作成したCachedFinderを載せておきます。

package main

import "fmt"

func CachedFinder() func(key string) string {
    cache := make(map[string]string)
    return func(key string) string {
        str := cache[key]
        if str != "" {
            fmt.Println("got at cache")
            return str
        }
        str = "abc" + key // webapiやdb readなど
        if str == "" {
            return ""
        } else {
            cache[key] = str
            return str
        }
    }
}

func main() {
    fmt.Println("Hello, 世界")
    finder := CachedFinder()
    a := finder("hogehoge")
    fmt.Println(a)
    b := finder("piyopiyo")
    fmt.Println(b)
    c := finder("hogehoge")
    fmt.Println(c)
}

二度目のhogehogeはcacheから取得できるようになっています。

defer

deferをつけると、遅延呼び出しが可能になります。タイミングは呼び出し元の関数の終わり(returnする)まで遅延させるという機能です。

関数の引数はすぐに評価されるが、呼び出しがreturnの直後になる点に注意が必要です。deferが複数ある場合は、stackされるため実行は下(後に宣言した方)から呼ばれます。LIFO(last-in-first-out)です。

これに関しても良い点、悪い点があります。

deferの利用するポイントの一つとしては、Close()をNew()した直後に書くとかが多いですが、メソッドの終端で実行するという仕組みであるため、New()したものからさらにNew()をするという箇所では使えなかったのが残念でした。

Go App Engineのテストパッケージに"google.golang.org/appengine/aetest"というのがあり、InstanceからContextを生成する箇所があります。このContextだけ利用したいという箇所があるのですが、InstanceをCloseするとContextも使い物にならなくなるので、CreateInstance()みたいなのを定義しCloseは書けませんでした。

func TestHogehoge(t *testing.T) {
    opt := &aetest.Options{
        AppID: "test",
        StronglyConsistentDatastore: true,
    }
    inst, _ := aetest.NewInstance(opt)
    defer inst.Close()
    req, _ := inst.NewRequest("POST", "/", nil)
    c := appengine.NewContext(req)
    // 処理
}

これを関数として切り出して

func CreateInstance() context.Context {
    opt := &aetest.Options{
        AppID: "test",
        StronglyConsistentDatastore: true,
    }
    inst, _ := aetest.NewInstance(opt)
    defer inst.Close()
    req, _ := inst.NewRequest("POST", "/", nil)
    return appengine.NewContext(req)
}

とやると、使えないContextが出てきます。残念です。

ちなみに上記コードの意味はdatastoreの一貫性を強制的に保つようにするフラグを立ててContextを生成するものです。datastoreのテストをするとよくあるのが、writeの直後にreadすると値がreadできないという状況が発生するため、それを回避するために利用しています。

pointer

ポインタというか、Go言語は基本的には値渡しになります。なので、高コストのstructを生成したり、内容を変更したい時に使います。java界からきた私は最初少し違和感がありましたが、これはこれでまぁありかなという感じです。

例としては

package main

import "fmt"

func swap(s string) {
    s = "world" // これだとsは変更できない
}

func pswap(s *string) {
    *s = "world" // pointerを通して変更する
}

func main() {
    s := "hello"
    swap(s)
    fmt.Println(s)
    pswap(&s)
    fmt.Println(s)
}

slice

sliceがoverflowした時には新しい配列が割り当てられます。
以下二つの実行結果の違いで分かるかと思いますが、

package main

import "fmt"

func main() {
    fmt.Println("overflow")
    a := make([]int, 0, 5)
    a = append(a, 1, 2, 3, 4)
    printSlice("a", a)
    b := append(a, 1, 2, 3, 4)
    printSlice("b", b)
    a[0] = 100
    printSlice("a", a)
    printSlice("b", b)

    fmt.Println("not overflow")
    c := make([]int, 0, 100)
    c = append(c, 1, 2, 3, 4)
    printSlice("c", c)
    d := append(c, 1, 2, 3, 4)
    printSlice("d", d)
    c[0] = 100
    printSlice("c", c)
    printSlice("d", d)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}

前者(a,b)はoverflowしたタイミングで別なsliceを割り当てるので、
aとbが最終的に示す0要素目の値が異なるようになります。

後者(c,d)の方はoverflowしない(makeでcapacityを100にしている)ので
最後のcとdのsliceの0要素目が示す値が同じ(100)になります。

なので、私が実装している時は基本的にappendの結果は自身で受け取るようにしています。

a = append(a, 1,2,3)
こんな感じですね。

methods

Goにはクラスは存在しません。struct型や任意の型にメソッドを定義できるので、メソッドレシーバー(funcの後に続く t *Typeなど)という呼ばれているようです。

interface

javaのinterfaceとかに似ていますね、単純なメソッドの集まりを定義していきます。

ただし、実装側は明示的にimplementsとか書かなくてよく、シグニチャレベルで一致しているかどうかの判断をしているようです。

type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    v := Vertex{3, 4}

    a = &v // a *Vertex implements Abser

    a = v
    fmt.Println(a.Abs())
}

type Vertex struct {
    X, Y float64
}

// これダメ。 *VertexがAbsを実装している定義となり、上の a = vでコケる。
// func (v *Vertex) Abs() float64 {
//  return math.Sqrt(v.X*v.X + v.Y*v.Y)
//}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

fmtパッケージにあるStringerというinterfaceがあり

type Stringer interface {
    String() string
}

という定義がされている。

そのため

type Person struct {
    Name string
    Age int 
}

func (p Person) String() string { // ここでStringerの実装
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    a := Person("Taro", 10)
    fmt.Println(a)  
}

とかが可能になります。

reader

何か作る上ではfile,webstreamのreadを取り扱う必要があるのは避けられないことです。Go言語にはデータストリームを読むことを表現するio.Readerインターフェースが存在します。

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Hello, Reader!")

    b := make([]byte, 8)
    for {
        n, err := r.Read(b)
        fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
        fmt.Printf("b[:n] = %q\n", b[:n])
        if err == io.EOF {
            break
        }
    }
}

tourそのままコピペしました。

出力結果が

n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""

こんな感じで出てきます。

Readerはbyte sliceに入れた数(n)とエラーを返しています。
fmt.Printfでbを見ると、きちんと1byte毎に分割されたHello, Reader!が出ていますね。

ASCII文字列の10進数で表現されているので対応表を載せておきました。
http://e-words.jp/p/r-ascii.html


さて以上が私がGo Tourをやってみた感想でした。

次回は、Google App Engineの開発でつまづいたところをまとめていきます。

最後に少しネタバレになりますが、Excersizeの解答例の一部を載せておきます。


Go Tour 回答例

Loops and Functions

package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
    z := 10000.0
    for i := 0; i < 10; i++ {
        z = z - (z * z - x) / (2 * z)
        fmt.Println(z)
    }
    return z
}

func main() {
    fmt.Println(Sqrt(2))
}

Slices

package main

import (
    "fmt"
    "golang.org/x/tour/pic"
)

func Pic(dx, dy int) [][]uint8 {
    fmt.Println(dx)
    fmt.Println(dy)
    ret := make([][]uint8, dy)
    for i := 0; i < dy; i++ {
        ret[i] = make([]uint8, dx)
        for j := 0; j < dx; j++ {
            ret[i][j] = uint8((i + j))
        }
    }
    return ret
}

func main() {
    pic.Show(Pic)
}

Maps

package main

import (
    "strings"
    "golang.org/x/tour/wc"
)

func WordCount(s string) map[string]int {
    m := make(map[string]int)
    for _, v := range strings.Fields(s) {
        m[v] += 1
    }
    return m
}

func main() {
    wc.Test(WordCount)
}

Fibonacci closure

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    v1 := 1
    v2 := 1
    return func() int {
        v1, v2 = v2, v1 + v2
        return v1
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

Stringers

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ipAddr IPAddr) String() string {
    return fmt.Sprintf("%v.%v.%v.%v", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]) 
}

func main() {
    addrs := map[string]IPAddr{
        "loopback":  {127, 0, 0, 1},
        "googleDNS": {8, 8, 8, 8},
    }
    for n, a := range addrs {
        fmt.Printf("%v: %v\n", n, a)
    }
}

Errors

package main

import (
    "fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprint("cannot Sqrt negative number: %f\n",
                      float64(e))
}

func Sqrt(x float64) (float64, error) {
    if x < 0.0 {
        return 0, ErrNegativeSqrt(x)
    }
    z := 10000.0
    for i := 0; i < 10; i++ {
        z = z - (z * z - x) / (2 * z)
        fmt.Println(z)
    }
    return z, nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

Reader

package main

import (
    "strings"
    "golang.org/x/tour/reader"
)

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (mr MyReader) Read(b []byte) (int, error) {
    r := strings.NewReader("A")
    return r.Read(b)
}

func main() {
    reader.Validate(MyReader{})
}

rot13Reader

package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func rot13(b byte) byte {
    if 'A' <= b && b <= 'Z' {
        k := b - 'A'
        return 'A' + (k-13+26)%26
    }
    if 'a' <= b && b <= 'z' {
        k := b - 'a'
        return 'a' + (k-13+26)%26
    }
    return b
}

func (rr rot13Reader) Read(b []byte) (int, error) {
    n, e := rr.r.Read(b)
    if e == io.EOF {
        return n, e
    }
    for i := 0; i < n; i++ {
        b[i] = rot13(b[i])
    }
    return n, e
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

では良いインプットと良いプログラミングを。

2016年2月21日日曜日

Webサービスにおける暗号化の基本 / SSL,https

mainpic

これまでの話で通信に関する暗号化の基礎的な要素の理解を進めてきました。

共通鍵と公開鍵という仕組み存在し、デジタル署名を行うことが可能でした。さらに、公開鍵の所有者を第三者が認める電子証明書があり、第三者機関を認証局と呼んでいましたね。

一番の脅威である「なりすまし」を認証局の仕組みを利用して防ぎ、デジタル署名を利用し「改ざん」が検知出来るようになり、鍵の仕組みをきちんと運用することにより「盗聴」されないように暗号化され、電子証明書から「否認」を防止できました。

さて、最後となる今回はこれらの技術を掛けあわせて動いているSSL: Secure Sockets Layerの仕組みをまとめていきます。


Webサービス開発における暗号化の基本

第四回目の今回は、

  1. 共通鍵暗号化方式
  2. 公開鍵暗号化方式
  3. デジタル署名(電子署名)
  4. 電子証明書
  5. 認証局
  6. オレオレ証明書
  7. SSLの仕組み(今日はここまで)

最後はSSL(https)の仕組みに関してです。


SSL: Secure Sockets Layer

SSLの仕組みの説明の前に、用語の整理を行います。

  • SSL
    TCP/IPネットワークで、データを暗号化して送受信するプロトコル。OSI参照モデルではセッション層に位置している。

  • https
    SSLによって提供されるセキュアな接続の上でアプリケーション層のhttp通信を行うこと。

  • TLS: Transport Layer Security
    SSLはNetscape Communicationsが開発したが、後にSSL3.0を元にTLS1.0としてRFCに定められた。特に区別する場合を除きSSLと同等のものである。

実はhttpsというプロトコルは存在せず、HTTP over SSL/TLSという二つのプロトコルを組み合わせたものをhttpsと呼んでいます。

SSLの仕組み

要約すると、クライアントがサーバーの電子証明書から公開鍵を取り出して共通鍵を暗号化しサーバーに返送、サーバーは秘密鍵を使って復号化し、その後は共通鍵暗号化方式で通信を行うというもの。

少し細かく見ていきましょう。

Created with Raphaël 2.1.2WebサーバーWebサーバークライアントクライアント認証局から電子証明書を発行してもらう。サーバー内に秘密鍵とセットで保持。サーバー秘密鍵電子証明書SSL接続要求電子証明書を送信認証局の電子証明書を確認(ルート証明書などを利用)認証局の公開鍵で復号化し電子証明書から公開鍵を取得サーバー公開鍵共通鍵を生成 *サーバー公開鍵を使って暗号化サーバー公開鍵暗号化された共通鍵共通鍵を送信秘密鍵を使って復号化サーバー秘密鍵電子証明書共通鍵共通鍵を使って通信共通鍵を使って通信

クライアントが共通鍵を生成する箇所がありますが、正確には共通鍵の基となるプリマスターシークレットというものを生成します。サーバーは受け取ったプリマスターシークレットを元にマスターシークレットを生成し、共通鍵を得ます。

仕組みは以上です。

実際にどのように組み込んでいるのか、シンプルなオレオレhttpsサーバーを構築してみました。

オレオレは「自己署名証明書」を利用することを意味していますw


nodejsによるSSL通信

まずはWebサービスにおける暗号化の基本 / 認証局でも作ったように、鍵、署名リクエスト、自己署名証明書を作成します。

openssl genrsa 2048 > my.key # 鍵
openssl req -new -key my.key > my.csr # 署名リクエスト
openssl x509 -days 3650 -req -signkey my.key < my.csr > my.crt # 自己署名証明書

これを使って、nodejsでサーバーを作るのは以下のようなソースになります。

var https = require('https');
var fs = require('fs');
var options = {
  key: fs.readFileSync('./my.key'),
  cert: fs.readFileSync('./my.crt')
};
https.createServer(options, function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
 }).listen(1338, '127.0.0.1');
 console.log('Server running at https://127.0.0.1:1338/');

my.keyとmy.crtを利用していますね。上記フロー図のWebサーバーのどこで利用されるのかが検討がつくかと思います。

これをsslserver.jsと名づけて保存し、

node sslserver.js

とすれば、https://127.0.0.1:1338でhttps通信することが出来ます。

ただし、自己署名証明書を利用しており、正しいCAを通過していないので以下のように赤くなり、信頼できませんといわれます。
https

以上がsslの仕組みとサンプルサーバーになります。

では、良いインプットと良いプログラミングを。

2016年2月19日金曜日

Webサービスにおける暗号化の基本 / 認証局

mainpic

Webサービス開発における暗号化の基本として、色々とまとめてきましたが、おそらく残り二回くらいで終わりにする予定です。

第一回の記事では共通鍵と公開鍵の話をまとめました。

第二回の記事ではデジタル署名に関しての話をまとめました。

第三回の記事では電子証明書の話をまとめました。

電子証明書とは、自分の公開鍵に認証局がデジタル署名をしたモノでした。

前回は特に深く追求をしませんでしたが、デジタル署名は本当にそれが送信者本人の署名であるのかは分からないという話が以前ありました。

では、認証局の署名というのは、何をもって正しいと判断されるのでしょうか。
認証局の仕組みと役割をまとめていきます。


Webサービス開発における暗号化の基本

第四回目の今回は、

  1. 共通鍵暗号化方式
  2. 公開鍵暗号化方式
  3. デジタル署名(電子署名)
  4. 電子証明書
  5. 認証局
  6. オレオレ証明書(今日はここまで)
  7. SSLの仕組み

認証局の仕組みに関してです。


認証局(CA: Certificate Authority)

認証局の役割は公開鍵に対して署名を行い、電子証明書を発行するということでした。

公開鍵を印鑑だとすると、電子証明書は印鑑証明書、認証局は市役所という捉え方もできます。

身分証明書を持って市役所に行き、手続きを踏むと印鑑証明書を発行してもらえます。何か契約を結ぶ際には、この印鑑と印鑑証明書を併せて提出することにより、この印鑑はあなた本人が間違いなくサインしたものですという意味を持つようになります。(「否認」を防ぐことが出来ますね。身分証明書の提出があるので「なりすまし」も難しいです。)

さて、先に少し話をしましたが、では認証局のデジタル署名は何をもって信頼するのか、その電子証明書は本当にその認証局が発行したものなのかという疑問を解決していきます。

認証局のデジタル署名

実は認証局のデジタル署名に用いられる認証局の公開鍵は、認証局が自ら証明するという流れを取っています。他に証明してくれる機関が存在しないためです。

自分で自分の公開鍵に対して署名を行った証明書のことを自己署名証明書(オレオレ証明書)と言います。

詳しくは後述しますが、大手の認証局はこの自己署名証明書がPC内に事前にインストールされており、認証局のデジタル署名の信頼を確保しています。この自己署名証明書を発行し、事前インストールを認められている機関をルート証明機関ます。

日本で有名な大手企業としては、SymantecGlobalSignがあります。

また、全ての証明書が大手の認証局によって管理されている訳ではなく、認証局が信頼した認証局というのが存在します。これを中間証明機関と呼んだりします。

sample

タブ内に中間証明機関とルート証明機関というのがあるのが分かりますね。

試しに中間証明機関の証明書を見てみると

sample2

この証明書はGeoTrust Global CAによって発行されているのが分かります。

この親と子という構造を取りながら、認証局というのは信頼され、その信頼された認証局から発行された電子証明書は信頼できる。という仕組みを取っています。


自己署名証明書(オレオレ証明書)

自分の公開鍵を自分で署名し、発行した証明書を自己署名証明書と呼びました。

ルート証明書を見てみます。

sample3

VeriSignの電子証明書です。

発行者がVeriSignになっており、確かに自分で署名を行っているのが確認できます。

私は開発の途中で、SSLの通信が必要になった時にオレオレ証明書を発行したりします。

自己署名証明書の発行

鍵の発行からまとめると以下のコマンドで発行できます。

 # 自分の秘密鍵の発行(RSA 2048bit)
openssl genrsa 2048 > my.key

# 発行リクエストCSRの発行
openssl req -new -key my.key > my.csr

# x509仕様の電子証明書を発行(my.csr利用、有効期間3,650日)
openssl x509 -days 3650 -req -signkey my.key < my.csr > my.crt

ここで出来たmy.crtが自己署名証明書になります。

crtを発行するために、電子署名をする必要があります。
電子署名は自分の秘密鍵でメッセージダイジェストを暗号化する技術でしたね。
そのため認証局用の鍵が必要です。
今回はca.keyという名前で作成しています。

さて、今回は認証局のデジタル署名はどうやって信頼されるのか、その技術である自己署名証明書について、またその発行の仕方が分かりました。

暗号化の基本は以上ですが、次回はこの証明書を使ってSSL通信を行いhttpsを覗いてみることにしましょう。

では、良いインプットと良いプログラミングを。

2016年2月17日水曜日

Webサービスにおける暗号化の基本 / 電子証明書

mainpic

引き続き暗号化に関しての記事を書いていきます。

前々回の記事では共通鍵と公開鍵の話をまとめました。

前回の記事ではデジタル署名に関しての話をまとめました。

しかし、どちらの技術を利用したとしても受信者が意図した受信者であること送信者が意図した送信者であることを確認することができず、「なりすまし」の脅威を排除するには至りませんでした。

それは、自分が情報を送受信したい本来の相手が持っている秘密鍵が、送受信時に利用されている鍵と同じ鍵か、というのが自分には分からないためです。

さて、どのように解決をするのか、まとめてみました。


Webサービス開発における暗号化の基本

第三回目の今回は、

  1. 共通鍵暗号化方式
  2. 公開鍵暗号化方式
  3. デジタル署名(電子署名)
  4. 電子証明書(今日はここまで)
  5. 認証局
  6. オレオレ証明書
  7. SSLの仕組み

今日は電子証明書について書いていきます。
認証局についても多少触れますが、詳細は次回に。


電子証明書

これまでの技術で問題となっているのは公開鍵が本人のものかどうか分からないということでした。

公開鍵暗号化方式では、受信者が公開鍵を送信者に渡します。この公開鍵が本当にあなたの友人のものであったり、あなたの取引先のものであったりが何らかの方法で確証が取れれば、データの送信を迷いなく行うことが出来ます。

デジタル署名では、送信者が公開鍵を受信者に渡します。この公開鍵が本当にあなたの友人のものであったり、あなたの取引先のものであったりが何らかの方法で確証が取れれば、データを受け取った時に付いている署名を信じて、送信者が送った内容を正しく判断することが出来ます。

電子証明書とは

この公開鍵が本人のものかの確証のことを電子証明書と言います。第三者によるお墨付きと言えるかもしれません。

実はブラウザやメーラーなどには標準である程度の電子証明書が組み込まれています。

sample

詳しくは後で説明をしますが、ここに登録されているような機関が中心となって電子証明書を発行しています。

このお墨付きを与える第三者機関のことを認証局と呼びます。

先に電子証明書とはどういったモノなのかを知っておきましょう。

電子証明書の規格はX.509という仕様になります。拡張子は.pem.der.crt.cer.pfx.p12などがあるようです。

私が扱ったことがあるのは、crt, p12くらいですが。
azureを利用するときにはp12をを求められることが多いです。

内容は、バージョン、シリアル番号、署名アルゴリズム(CAがつけたデジタル署名のアルゴリズム)、発行者有効期間、公開鍵の所有者情報、公開鍵、拇印(認証局のメッセージダイジェスト)、拇印アルゴリズム(認証局のメッセージダイジェストのアルゴリズム)…などです。

興味のある方はご自身の環境で確認してみてください。
chromeの場合は、右上の → 設定 → 詳細設定を表示… → 証明書の管理 → 信頼されたルート証明機関 → いずれかを選択 → 詳細
で確認できます。

この電子証明書に対して認証局がデジタル署名を行います。(そのため電子証明書の内容には、拇印と拇印アルゴリズムがあります。)

 

少しややこしくなってきましたね。

単純に電子証明書が転がっていたとしても、それが正規の認証局によって本人の確証が得られたモノなのかが分かりません。

なので、認証局が電子証明書にデジタル署名をすることによりこの電子証明書は認証局が認めたモノですというのを示しています。

この認証局がデジタル署名をするときにに、電子証明書に対してハッシュ関数(拇印アルゴリズム)を適用し、メッセージダイジェスト(拇印)を電子証明書に収めます。

これで内容の改ざんを防ぎます。

さらに、この拇印に対して署名アルゴリズムを適用し、署名を行っています。

 

これで、Webサイト等の公開鍵をもらうときには以下の流れで確証を得ることが出来ます。

  1. Webサイト等から電子証明書を受取る
  2. 電子証明書に付いている署名を確認する。
  3. 署名と認証局の公開鍵を使い拇印を取得する。
    (署名アルゴリズムの適用)
  4. 拇印からハッシュ値を取得する。
    (拇印アルゴリズムの適用)
  5. 電子証明書自体に拇印アルゴリズムを適用しハッシュ値を得る。
  6. 4.と5.のハッシュ値を比較し、内容が正しいことを確認。
  7. 認証局により認められたWebサイトの公開鍵を取得!

 

では電子証明書を取得するにはどうしたら良いのでしょうか。


電子証明書の取得

電子証明書は認証局が発行するモノです。電子証明書にはあなたが作成した秘密鍵に対する公開鍵を含める必要があります。

そのため、認証局に公開鍵とともに申請を行い、発行してもらいます。

まず、鍵のペアを作成します。(認証局のWebページ上から必要な情報を入力すると作成してくれるサービスを提供している認証局もあります。)

macやlinuxであれば

$ openssl genrsa 2048 > my.key

これで2048bitのRSAの秘密鍵が出来上がります。
もし公開鍵も同時に作りたい場合は以下のコマンドを実行します。

余談ですが、鍵を使ったssh接続をするときはこちらのコマンドを使いますね。

$ ssh-keygen -t rsa

すると次のような質問が出てくるので、必要な箇所を埋めていきます。

$ Enter file in which to save the key (defaultpath):
$ Enter passphrase (empty for no passphrase):
$ Enter same passphrase again:
$ Your identification has been saved in defaultpath/id_rsa.
$ Your public key has been saved in defaultpath/id_rsa.pub.

これで秘密鍵と公開鍵が得られます。(id_rsaが秘密鍵、id_rsa.pubが公開鍵)

この公開鍵を認証局に渡すのですが、私がこれまで電子証明書をもらうときは直接渡すのではなく、署名リクエスト(CSR: Certificate Signing Request)を作成し送っています。

このCSRの作り方はこのコマンドを実行します。

openssl req -new -key my.key > my.csr

また色々入力を求められるので埋めます。

Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:

これでmy.csrを使って認証局に証明書を作成してもらえるようになりました。

これを認証今日のWebページから送信すると後日証明書を受取ることが出来るようになります。

今日は以上です!次回は認証局に関してまとめていきます。
では、良いインプットと良いプログラミングを。

2016年2月16日火曜日

Webサービスにおける暗号化の基本 / デジタル署名

mainpic

前回の記事ではWebサービスにおける暗号化の基礎として、共通鍵と公開鍵の話をまとめました。

共通鍵は同じ鍵を使って暗号通信を行う。
公開鍵はデータ受信者が秘密鍵・公開鍵作成し、暗号通信を行うということでした。

しかし、強固に見える公開鍵暗号化方式でも「送信者が意図した受信者かどうか」を確かめる術がなく、「なりすまし」を防ぐことが出来ません。

また、同様に受信者側から見ても送信者が本当に自分の知っている送信者なのかを確かめることが出来ません。

そこで、引き続きその後に続く暗号化の周辺知識を足していきます。


Webサービス開発における暗号化の基本

第二回目の今回は、

  1. 共通鍵暗号化方式
  2. 公開鍵暗号化方式
  3. デジタル署名(電子署名)(今日はここまで)
  4. 電子証明書
  5. 認証局
  6. オレオレ証明書
  7. SSLの仕組み

署名に関して書いていきます。


デジタル署名(電子署名)

インターネット上で自分が自分であることを証明する「署名(サイン)」です。

メールに付けて送信する方もいらっしゃいますね。

概念としては実世界と同じように、本人筆跡のサインや捺印というものと同じイメージです。大切な書類を発行してもらうときに発行元がサインしたりしますよね。そのようなイメージです。

厳密には、紙面上でのサインを代替する技術の総称を「電子署名」と呼び、デジタル署名は電子署名技術の一つです。

このデジタル署名自体は暗号化ではなく、送信したい情報に添付するただの署名であるということを覚えておいてください。

仕組み自体は非常に簡単です。

デジタル署名の流れは公開鍵暗号化方式の応用で、自分の秘密鍵で暗号化した乱数(メッセージダイジェスト、後述)を相手に送信することです。

すると、相手はあなたの公開鍵を使い暗号化された乱数を復号化することができます。あなたの公開鍵できちんの復号化されたならば、それを送信してきたのは秘密鍵を持っているあなたに間違いないということです。

これによって、「なりすまし」、「改ざん」、「盗聴」、「否認」のうちの一つ、「否認」を防ぐことができます。

なぜなら、送信者が本人であるという証明が送付され、それが受信者側で確認されたためです。

この流れの中にある、自分の秘密鍵で暗号化した乱数のことをデジタル署名と言います。

この乱数はただの乱数ではなく、メッセージダイジェストと呼ばれる送信したい内容から「ハッシュ関数」という単方向関数(不可逆)を用いて作成したビットデータです。


メッセージダイジェストについて

適当な例をあげるとすれば

Hello World!! (送信したい情報)

↓ ハッシュ関数適用!

0101011010110110000111 (メッセージダイジェスト)

という感じです。

メッセージダイジェストには
1. 不可逆の変換であり、原文を復元できない
2. 1bitでも異なると異なる値を出力する
3. 同じハッシュ値を持つデータを作成することは難しい。
という3つの特徴があります。


デジタル署名の流れ

メッセージダイジェストの特徴を利用して以下の流れのように署名が行われます。

Created with Raphaël 2.1.2送信者送信者受信者受信者本文: "Hello"Helloにハッシュ関数適用 → 01100111本文: "Hello"、MD: 0110011101100111を秘密鍵で暗号化Hello + デジタル署名 + 公開鍵Hello + デジタル署名 + 公開鍵公開鍵でデジタル署名を復号化本文: "Hello"、MD: 01100111Helloにハッシュ関数適用 → 01100111作成したハッシュ値と送信されたハッシュ値が同じことを確認

実は、情報の内容が1bitでも異なるというメッセージダイジェストの特徴から、「改ざん」を防ぐこともできます。もし「改ざん」が行われていると送信されたハッシュ値自分が本文から作成したハッシュ値に差異が生じるためです。

以上がデジタル署名の流れとなります。


それでもまだ足りない。

実は結果から言うと、デジタル署名では脅威である「なりすまし」、「改ざん」、「盗聴」、「否認」のうち「なりすまし」を防ぐことは出来ず、前回の記事と同じ問題を抱えたままです。

どういうことかと言うと、proxyなどを介して全ての通信を盗む悪人がいるとすると、送信者の情報を抜き、改ざんし、デジタル署名を破棄し、悪人が送信者のふりをしたデジタル署名を付け、全く別な情報を受信者に送信する。

という事ができてしまいます。受信者から見ると、意図した送信者かどうかを判断することはできないのです。

流れはこんな感じです。

Created with Raphaël 2.1.2送信者送信者悪人悪人受信者受信者Hello + デジタル署名 + 公開鍵Hello → ByeByeデジタル署名 → 'デジタル署名公開鍵 → '公開鍵ByeBye + 'デジタル署名 + '公開鍵ByeBye 'デジタル署名 '公開鍵ハッシュ値が一致してしまう

デジタル署名を使っても

公開鍵の作成者が受信者本人であるかが不明

という状況は変わらないため、これを解決する必要があります。

それを解決するのが次回説明する電子証明書と認証局という仕組みになります。

今日は以上です!

では、良いインプットと良いプログラミングを。