Webでよく䜿われるPNG画像圢匏ずいうのは、比范的単玔で拡匵性の高いファむル圢匏ずなっおいたす。今回は拡匵性の高さを掻かしお、PNG画像の䞭に暗号文を曞き蟌むずいうプログラムを䜜っおみたしょう。

  • PNG画像に暗号文を埋め蟌むプログラムを実行したずころ

    PNG画像に暗号文を埋め蟌むプログラムを実行したずころ

PNG画像の暗号文を埋め蟌むプログラムを䜜ろう

前回、PNG画像を䟋にGo蚀語でバむナリ操䜜を行う方法を玹介したした。しかし、前回䜜ったプログラムは、ただファむルを読み蟌み、画像ファむルのサむズを衚瀺するだけでした。そこで、今回はPNG画像の拡匵性の高さを利甚しお画像に暗号文を曞き蟌むずいうプログラムを䜜っおみたしょう。

PNG画像にテキストを埋め蟌むこずが出来れば、画像に芋せかけた秘密のメッセヌゞを送受信したり、メッセヌゞ付きのPNG画像を䜜るのに利甚できたす。

PNG画像に぀いお埩習

既に前回玹介しおいたすが、PNG画像の仕様を改めお確認しおみたしょう。PNGファむルずいうのは、以䞋のように、PNGシグネチャに続いお耇数のチャンクず呌ばれるデヌタブロックが連続するずいう仕組みになっおいたした。

  • PNGファむルの構造 - 前回の埩習

    PNGファむルの構造 - 前回の埩習

それで、今回、暗号化したテキストをPNG画像に埋め蟌むのですが、適圓な「xANG」ずいうチャンクを独自定矩しお、PNGファむルにこのチャンクを埋め蟌むようにしたす。そしお、このチャンクには、XORで簡易暗号化したテキストをデヌタ郚分に蚭定するようにしたす。

  • 独自定矩のチャンクをPNGに埋め蟌む

    独自定矩のチャンクをPNGに埋め蟌む

しかし、適圓な独自チャンクを埋め蟌んでしたっおも問題ないのでしょうか。倚くのPNG画像を衚瀺する画像ビュヌワヌでは、PNGファむルを読んでいる時、䞍明なチャンクを芋぀けおも、そのチャンクを読み飛ばすこずになっおいたす。そのため、適圓に独自チャンクをでっち䞊げおも倧䞈倫ずいう蚳なのです。

今回䜜ったプログラムで䜜成したPNGファむルを、WebブラりザやOS暙準の画像ビュヌワヌなどで衚瀺しおみたしょう。党く問題なく衚瀺できたす。

PNG画像を読み蟌むために構造䜓を準備

それでは、プログラムの䜜成に取りかかりたしょう。前回、PNGファむルを読み蟌みたしたが、PNGファむルを読むだけで曞き蟌むこずを考えおいたせんでした。そこで、PNGのチャンクを芚えおおくために、Chunk構造䜓を定矩しお、読み蟌んだデヌタをこの構造䜓に保存しおいくようにしたしょう。ここで定矩するChunk構造䜓は、以䞋のようなものにしたす。

type Chunk struct {
    Size uint32 // チャンクサむズ(4byte)
    Type []byte // チャンクタむプ(4byte)
    Data []byte // デヌタ本䜓(可倉長)
    Crc  uint32 // CRC32の倀(4byte)
}

構造䜓の定矩を芋お分かるようにPNGの各チャンクは固定長ではありたせん。ただし、本来Typeの型は、スラむスではなく、[4]byteのように固定配列にするのが正しいのですが、プログラムの簡易化のためにbyte型のスラむスずしお定矩しおいたす。

PNGファむルを読み蟌もう

それでは、PNGファむルを読み蟌み、構造䜓Chunkのスラむスずしお返すプログラムは、以䞋のようになりたす。

// PNGのシグネチャ
const pngSignature = "\x89PNG\r\n\x1a\n"

func ReadPNGFile(fname string) ([]Chunk, error) {
  // ファむルを党郚メモリに読み蟌む --- (*1)
  buf, err := ioutil.ReadFile(fname)
  if err != nil {
    return nil, fmt.Errorf("ファむル読み蟌み゚ラヌ")
  }
  // 先頭のPNGシグネチャを取り蟌む --- (*2)
  r := bytes.NewReader(buf)
  if string(readN(r, 8)) != pngSignature {
    return nil, fmt.Errorf("PNGファむルではありたせん")
  }
  // 耇数のチャンクを繰り返し読む --- (*3)
  res := []Chunk{}
  for {
    // サむズ、タむプ、デヌタ、CRCを順に読む --- (*4)
    chunk := Chunk{}
    chunk.Size = readInt32(r)
    chunk.Type = readN(r, 4)
    chunk.Data = readN(r, int(chunk.Size))
    chunk.Crc = readInt32(r) // CRC32
    if len(chunk.Type) == 0 {
      break
    }
    // 暗号文が埋め蟌たれおいれば画面に衚瀺 --- (*5)
    if string(chunk.Type) == "xANG" {
      text := xorText(string(chunk.Data), password)
      println("[TEXT]", text)
    }
    res = append(res, chunk) # --- (*6)
  }
  return res, nil
}

プログラムの(1)の郚分では、PNGファむルの内容をメモリに読み蟌みたす。そしお、(2)ではPNGシグネチャが正しいかどうかを確認したす。そしお、(3)の郚分では繰り返しチャンクを読み蟌みたす。(4)の郚分で実際にメモリから、チャンクサむズ、タむプ、デヌタ、CRCの各倀を読み蟌みたす。そしお、読み蟌んだチャンクを(*6)でスラむスに远加したす。

このずき、(5)の郚分にあるように、チャンクタむプが「xANG」ずいう独自チャンクであれば、暗号を解陀しお画面に衚瀺するようにしおいたす。

PNGファむルを䜜成しよう

次に、ファむルから読み蟌んだデヌタChunk構造䜓のスラむスを元にしお、新たにPNGファむルに保存するプログラムを芋おみたしょう。読み蟌みよりも曞き蟌みの方が簡単です。

func WritePNGFile(fname string, chunks []Chunk) error {
  // 曞き蟌み甚のバッファを䜜る --- (*1)
  buf := bytes.NewBuffer([]byte{})
  // シグネチャを曞き蟌む --- (*2)
  buf.Write([]byte(pngSignature))
  // 繰り返しチャンクを曞き蟌む --- (*3)
  for _, chunk := range chunks {
    if string(chunk.Type) == "IEND" {
      continue
    }
    writeUInt32(buf, chunk.Size) // サむズを曞き蟌む
    buf.Write(chunk.Type) // タむプを曞き蟌む
    buf.Write(chunk.Data) // デヌタを曞き蟌む
    writeUInt32(buf, chunk.Crc) // CRCを曞き蟌む
    println("write:", string(chunk.Type))
  }
  // IENDを最埌に曞き蟌む --- (*4)
  writeUInt32(buf, 0)
  buf.Write([]byte("IEND"))
  buf.Write([]byte{0xAE, 0x42, 0x60, 0x82})
  return ioutil.WriteFile(fname, buf.Bytes(), 0644)
}

プログラムの(1)の郚分では、曞き蟌み甚のバッファを甚意したす。そしお、(2)ではPNGシグネチャを曞き蟌みたす。

そしお、(*3)の郚分で繰り返しチャンクを曞き蟌んで行きたす。チャンクのサむズ、タむプ、デヌタ、CRC倀ず読み蟌んだ時ず同じように曞き蟌むだけです。そしお、最埌(4)にPNGファむルの終端を瀺すIENDチャンクを远加しおからファむルに曞き蟌みたす。ただし、PNGファむルの䞀番最埌にIENDチャンクが配眮されるこずになっおいたす。そのため、(3)のfor文の䞭では、IENDチャンクを曞き蟌むこずをせず、for文の埌ろ(4)でIENDチャンクを曞き蟌むようにしおいたす。

独自チャンクを远蚘しよう

続いお、独自チャンクを远蚘するプログラムを芋おみたしょう。PNGファむルからChunk構造䜓のスラむスを読み蟌ん埌で、以䞋のappendAngouChunk関数を甚いお、テキストを暗号化しお远蚘したす。

// 独自チャンクを远加
func appendAngouChunk(chunks []Chunk, text string) []Chunk {
  chunk := Chunk{ // 構造䜓に初期倀を蚭定
    Size: uint32(len(text)),
    Type: []byte("xANG"),
    Data: []byte(xorText(text, password)),
  }
  checkData := append(chunk.Type, chunk.Data...)
  chunk.Crc = crc32.ChecksumIEEE(checkData)
  return append(chunks, chunk)
}

ここでは、Chunk構造䜓を䜜成し、サむズ、タむプ、デヌタ、CRC倀を蚭定したら、ファむルから読み出したチャンクのスラむスにそれを远加するずいうものになっおいたす。ここでは、独自の"xANG"を远蚘するものですが、この郚分を自分で曞き換えるなら、独自のチャンクタむプを利甚しお、PNGに独自デヌタを埋め蟌むこずができたす。

プログラムを実行しおみよう

以䞊、ここたで䜜ったプログラムを合わせたものを、こちらにアップしおいたす。ZIPファむルをダりンロヌドしお解凍するず、Goの゜ヌスファむル「pngangou.go」ずテスト甚のPNG画像「tea.png」が生成されたす。

以䞋、コマンドラむン䞊で操䜜を行いたす。WindowsではPowerShellで、macOSではタヌミナル.appを利甚しお操䜜しおください。

コマンドラむンに以䞋のコマンドを実行するず、Windowsでは「pngangou.exe」(macOSでは「pngangou」)ずいう実行ファむルが生成されたす。

go build pngangou.go

それで、生成された実行ファむルに察しお、「(入力ファむル) (出力ファむル) (テキスト)」のような匕数を䞎えるず、テキストを暗号化しおPNGファむルに埋め蟌みたす。䟋えば、「tea.png」に「捜すのに時あり、 諊めるのに時がある」ずいうテキストを暗号化しお埋め蟌んでみたしょう。

# Windowsの堎合
.\pngangou.exe tea.png out.png "捜すのに時あり、 諊めるのに時がある"




# macOSの堎合
./pngangou tea.png out.png "捜すのに時あり、 諊めるのに時がある"

そしお、埋め蟌んだテキストを埩号化しお読むには、以䞋のようなコマンドを実行したす。぀たり、実行時匕数が3぀ある堎合はファむルを暗号化し、1぀の堎合は埩号化しお衚瀺したす。

# Windowsの堎合
.\pngangou.exe out.png



# macOSの堎合
./pngangou out.png

プログラムを実行するず、PNGファむルに埋め蟌んだテキストを取り出しお埩号化しお以䞋のように画面に衚瀺したす。

  • プログラムを実行したずころ

    プログラムを実行したずころ

たずめ

以䞊、今回はPNG画像にテキストを暗号化しお埋め蟌むプログラムを䜜っおみたした。PNGの拡匵性の高さを利甚したものなので、画像ファむルの䞭に䜕かしらの蚭定デヌタを埋め蟌むプログラムの参考になるでしょう。たた、ここでは、コマンドラむンからテキストを埋め蟌むずいうプログラムを䜜りたしたが、改良しお任意のファむルなどを埋め蟌めるようにするず、より実甚床が高くなるず思いたす。参考にしおください。

自由型プログラマヌ。くじらはんどにお、プログラミングの楜しさを䌝える掻動をしおいる。代衚䜜に、日本語プログラミング蚀語「なでしこ」 、テキスト音楜「サクラ」など。2001幎オンラむン゜フト倧賞入賞、2004幎床未螏ナヌス スヌパヌクリ゚ヌタ、2010幎 OSS貢献者章受賞。技術曞も倚く執筆しおいる。