システムプログラミングgolang

## はじめに

Goならわかるシステムプログラミングで、定期的に学んでいます。

https://ascii.jp/serialarticles/1235262/

今回は、PNG画像にtEXtチャンクを使って、秘密のテキストを追加します。

https://ascii.jp/elem/000/001/260/1260449/

※ 今回私が書かせていただく、Qiita記事は、こちら（ https://ascii.jp/elem/000/001/260/1260449/ ）の補足説明、検証しやすい形に整えたものです。より深く理解したい向けのアドバイスとしては、Qiitaを読んで、イメージを掴んだら、元リンクをじっくり読むことをおすすめします。



## やることイメージ共有


PNG画像は、このようなチャンクの構図になっています。
※ CRCは単体テストのようなチャンクです。

![スクリーンショット 2021-04-12 12.27.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300592/64b371ea-2bbd-d9fe-6c7a-67ceee193aa3.png)

PNG画像に、秘密のテキストを追加するためのtEXtというチャンクを使って、以下イメージ図のように追加します。

![スクリーンショット 2021-04-12 12.30.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300592/c6765d53-a695-7682-6a71-42d4d2380596.png)


## PNGファイルのチャンクを表示する処理


以下が、PNGファイルのチャンクを表示させる処理です。ここにPNG画像（この記事ではFuga.png）を用意して、この処理を実行してみましょう。



```golang:displayChunk.go

package main

import (
    "encoding/binary"
    "fmt"
    "io"
    "os"
    "bytes"
)

func dumpChunk(chunk io.Reader) {
    var length int32
    binary.Read(chunk, binary.BigEndian, &length)
    buffer := make([]byte, 4)
    chunk.Read(buffer)
    fmt.Printf("chunk '%v' (%d bytes)\n", string(buffer), length)
}

func readChunks(file *os.File) []io.Reader {
    // チャンクを格納する配列
    var chunks []io.Reader

    // 最初の8バイトを飛ばす
    file.Seek(8, 0)
    var offset int64 = 8

    for {
        var length int32
        err := binary.Read(file, binary.BigEndian, &length)
        if err == io.EOF {
            break
        }
        chunks = append(chunks, io.NewSectionReader(file, offset, int64(length)+12))
        // 次のチャンクの先頭に移動
        // 現在位置は長さを読み終わった箇所なので
        // チャンク名(4バイト) + データ長 + CRC(4バイト)先に移動
        offset, _ = file.Seek(int64(length+8), 1)
    }
    return chunks
}

func main() {
    file, err := os.Open("Fuga.png")
    if err != nil {
        panic(err)
    }
    chunks := readChunks(file)
    for _, chunk := range chunks {
        dumpChunk(chunk)
    }
}

```

**結果**

```
$ go run displayChunk.go
chunk 'IHDR' (13 bytes)
chunk 'tIME' (7 bytes)
chunk 'pHYs' (9 bytes)
chunk 'gAMA' (4 bytes)
chunk 'IDAT' (63773 bytes)
chunk 'IEND' (0 bytes)
```

## PNG画像をチャンクごとに分解して、秘密のテキストを追加する処理。


```golang:addTextChunk.go

package main

import (
    "encoding/binary"
    "fmt"
    "io"
    "os"
    "hash/crc32"
    "bytes"
)

func textChunk(text string) io.Reader {
    byteData := []byte(text)
    var buffer bytes.Buffer
    binary.Write(&buffer, binary.BigEndian, int32(len(byteData)))
    buffer.WriteString("tEXt")
    buffer.Write(byteData)
    // CRCを計算して追加
    crc := crc32.NewIEEE()
    io.WriteString(crc, "tEXt")
    binary.Write(&buffer, binary.BigEndian, crc.Sum32())
    return &buffer
}

func dumpChunk(chunk io.Reader) {
    var length int32
    binary.Read(chunk, binary.BigEndian, &length)
    buffer := make([]byte, 4)
    chunk.Read(buffer)
    fmt.Printf("chunk '%v' (%d bytes)\n", string(buffer), length)
}

func readChunks(file *os.File) []io.Reader {
    // チャンクを格納する配列
    var chunks []io.Reader

    // 最初の8バイトを飛ばす
    file.Seek(8, 0)
    var offset int64 = 8

    for {
        var length int32
        err := binary.Read(file, binary.BigEndian, &length)
        if err == io.EOF {
            break
        }
        chunks = append(chunks, io.NewSectionReader(file, offset, int64(length)+12))
        // 次のチャンクの先頭に移動
        // 現在位置は長さを読み終わった箇所なので
        // チャンク名(4バイト) + データ長 + CRC(4バイト)先に移動
        offset, _ = file.Seek(int64(length+8), 1)
    }
    return chunks
}

func main() {
    file, err := os.Open("Fuga.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    newFile, err := os.Create("FugaX.png")
    if err != nil {
        panic(err)
    }
    defer newFile.Close()
    chunks := readChunks(file)
    // シグニチャ書き込み
    io.WriteString(newFile, "\x89PNG\r\n\x1a\n")
    // 先頭に必要なIHDRチャンクを書き込み
    io.Copy(newFile, chunks[0])
    // テキストチャンクを追加
    io.Copy(newFile, textChunk("秘密のメッセージ"))
    // 残りのチャンクを追加
    for _, chunk := range chunks[1:] {
        io.Copy(newFile, chunk)
    }
}
```

addTextChunk.goを実行すると、秘密のテキストが挿入された新しいPNG画像（FugaX.png)が生成されます。

## 秘密のテキストが追加された新しいPNG画像のチャンク構造を表示する処理

はじめに実行したdisplayChunk.goを以下のように変更します。


-  指定PNG画像をFuga.pngからFugaX.pngに変更

- 秘密のテキストを確認するための以下の処理をdumpChunk()に追加する。

```golang
if bytes.Equal(buffer, []byte("tEXt")) {
    rawText := make([]byte, length)
    chunk.Read(rawText)
    fmt.Println(string(rawText))
}
```

コマンドを叩いて、displayChunk.goを実行してみましょう！

```
$ go run displayChunk.go
chunk 'IHDR' (13 bytes)
chunk 'tEXt' (24 bytes)
秘密のメッセージ
chunk 'tIME' (7 bytes)
chunk 'pHYs' (9 bytes)
chunk 'gAMA' (4 bytes)
chunk 'IDAT' (63773 bytes)
chunk 'IEND' (0 bytes)
```

秘密のメッセージを確認することが出来ました！

このような感じで秘密の文章を挿入することが出来ますので、是非試してみてくださいね！