2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ブロックチェーンに個人情報を保存してはいけない

Last updated at Posted at 2020-03-11

#はじめに
Hyperledger Fabricのデータベースへ個人情報は保存すべきでない…周回遅れ過ぎる話題かもしれないが、啓蒙活動の意味で投稿する。
国内の個人情報保護法では、サービス利用者から個人情報の削除を申し立てられても、合意のもとで集められたものならば企業側は「削除義務」しか発生しない。しかしながら、全ての情報が合意のもとで集められたものでは無いかもしれないし、大きな企業ならばイメージもあるので削除できるに越したことはない。
欧州のGDPRはその合意もサービス利用者の都合で破棄できる(たぶん)から、個人情報をブロックチェーン上から抹消可能である必要がある。

#World Stateからは削除できる
DelStateメソッドを使えばWorld State(HLFで言うところのデータベース)からは削除することができる。しかし、取引履歴には残ってしまうため、データの完全削除は不可能である。以下で検証する。

#データの準備
検証のため戦国武将の個人情報(名前)と機密情報(石高)のデータを準備する。
参照先リンク

Key 氏名 石高(万石)
0000000001 前田利長 120
0000000002 島津家久 73
0000000003 伊達政宗 62
0000000004 徳川義直 62
0000000005 徳川頼宣 56
0000000006 細川忠興 54
0000000007 黒田長政 47
0000000008 浅野幸長 43
0000000009 毛利輝元 36
0000000010 鍋島勝茂 36

当時の武将にとっては、自藩の石高は機密情報だったのではないだろうか。
隣の藩が5万石と思って攻め込んだら実は10万石のリソースがあって、倍の兵力で返り討ちにあったりすると大変である。今も昔も情報は大切。
#その石高を削ぐために参勤交代をしていたような

#データ定義
チェインコードはGolangで実装する。データ定義は次の通り。

type Asset struct {
        Name     string `json:"name"`     // 氏名
        Kokudaka int    `json:"kokudaka"` // 石高(万石)
}

何のひねりもない。石高はResourceにしようかと考えたが、「万石」を単位にしたかったのでKokudakaにしてしまった。「加賀百万石」みたいな響きがカコイイ。
ちなみに、データベースはCouchDBを使用する。

#データの登録、参照、更新、削除

登録
func (s *SmartContract) createAsset(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

        fmt.Println("createAsset:START")
        if len(args) != 3 {
                return shim.Error("Incorrect number of arguments. Expecting 3")
        }

        koku, _ := strconv.Atoi(args[2])
        asset := Asset{Name: args[1], Kokudaka: koku}
        fmt.Println("asset", asset)
        assetAsBytes, _ := json.Marshal(asset)
        APIstub.PutState(args[0], assetAsBytes)

        fmt.Println("createAsset:END")
        return shim.Success(nil)
}
参照(1件)
func (s *SmartContract) queryAsset(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

        fmt.Println("queryAsset:START")
        if len(args) != 1 {
                return shim.Error("Incorrect number of arguments. Expecting 1")
        }

        fmt.Println("- queryAsset:key =", args[0])
        assetAsBytes, _ := APIstub.GetState(args[0])
        var buffer bytes.Buffer
        buffer.WriteString("{\"Key\":")
        buffer.WriteString("\"")
        buffer.WriteString(args[0])
        buffer.WriteString("\"")

        buffer.WriteString(", \"Record\":")
        // Record is a JSON object, so we write as-is
        buffer.WriteString(string(assetAsBytes))
        buffer.WriteString("}")

        fmt.Println("queryAsset:END")
        return shim.Success(buffer.Bytes())
}
参照(全件)
func (s *SmartContract) queryAllAsset(APIstub shim.ChaincodeStubInterface) sc.Response {
        fmt.Println("queryAllAsset:START")

        startKey := ""
        endKey := ""

        resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
        if err != nil {
                return shim.Error(err.Error())
        }
        defer resultsIterator.Close()

        // buffer is a JSON array containing QueryResults
        var buffer bytes.Buffer
        buffer.WriteString("[")

        bArrayMemberAlreadyWritten := false
        for resultsIterator.HasNext() {
                queryResponse, err := resultsIterator.Next()
                if err != nil {
                        return shim.Error(err.Error())
                }
                // Add a comma before array members, suppress it for the first array member
                if bArrayMemberAlreadyWritten == true {
                        buffer.WriteString(",")
                }
                buffer.WriteString("{\"Key\":")
                buffer.WriteString("\"")
                buffer.WriteString(queryResponse.Key)
                buffer.WriteString("\"")

                buffer.WriteString(", \"Record\":")
                // Record is a JSON object, so we write as-is
                buffer.WriteString(string(queryResponse.Value))
                buffer.WriteString("}")
                bArrayMemberAlreadyWritten = true
        }
        buffer.WriteString("]")

        fmt.Printf("- queryAllAsset:\n%s\n", buffer.String())

        fmt.Println("queryAllAsset:END")
        return shim.Success(buffer.Bytes())
}
更新
func (s *SmartContract) updateAsset(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

        fmt.Println("updateAsset:START")
        if len(args) != 2 {
                return shim.Error("Incorrect number of arguments. Expecting 2")
        }
        assetAsBytes, _ := APIstub.GetState(args[0])
        asset := new(Asset)
        json.Unmarshal(assetAsBytes, &asset)

        koku, _ := strconv.Atoi(args[1])

        asset.Kokudaka = koku

        fmt.Println("asset", asset)
        assetAsBytes, _ = json.Marshal(asset)
        APIstub.PutState(args[0], assetAsBytes)

        fmt.Println("updateAsset:END")
        return shim.Success(nil)
}

#検証-1 データ登録、参照、更新
登録したデータを参照。

root@liva-z:tst# ./qallAsset.sh 
[{"Key":"0000000001", "Record":{"kokudaka":120,"name":"前田利長"}},{"Key":"0000000002", "Record":{"kokudaka":73,"name":"島津家久"}},{"Key":"0000000003", "Record":{"kokudaka":62,"name":"伊達政宗"}},{"Key":"0000000004", "Record":{"kokudaka":62,"name":"徳川義直"}},{"Key":"0000000005", "Record":{"kokudaka":56,"name":"徳川頼宣"}},{"Key":"0000000006", "Record":{"kokudaka":54,"name":"細川忠興"}},{"Key":"0000000007", "Record":{"kokudaka":47,"name":"黒田長政"}},{"Key":"0000000008", "Record":{"kokudaka":43,"name":"浅野幸長"}},{"Key":"0000000009", "Record":{"kokudaka":36,"name":"毛利輝元"}},{"Key":"0000000010", "Record":{"kokudaka":36,"name":"鍋島勝茂"}}]

Futonでも確認する。レコード数が10ある。
image.png

伊達政宗のレコードを確認してみる。
image.png

伊達政宗の石高を62から70へ変更して参照する。

root@liva-z:tst# ./updateAsset.sh 0000000003 70
2020-03-11 21:00:23.019 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200

更新できたか確認。

root@liva-z:tst# ./queryAsset.sh 0000000003
{"Key":"0000000003", "Record":{"kokudaka":70,"name":"伊達政宗"}}

Futonでも確認。
image.png
石高、増えてるね!

#検証-2 データ削除
伊達政宗のレコードを削除する。

root@liva-z:tst# ./deleteAsset.sh 0000000003
2020-03-11 21:06:06.284 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200

削除できたか確認。

root@liva-z:tst# ./qallAsset.sh 
[{"Key":"0000000001", "Record":{"kokudaka":120,"name":"前田利長"}},{"Key":"0000000002", "Record":{"kokudaka":73,"name":"島津家久"}},{"Key":"0000000004", "Record":{"kokudaka":62,"name":"徳川義直"}},{"Key":"0000000005", "Record":{"kokudaka":56,"name":"徳川頼宣"}},{"Key":"0000000006", "Record":{"kokudaka":54,"name":"細川忠興"}},{"Key":"0000000007", "Record":{"kokudaka":47,"name":"黒田長政"}},{"Key":"0000000008", "Record":{"kokudaka":43,"name":"浅野幸長"}},{"Key":"0000000009", "Record":{"kokudaka":36,"name":"毛利輝元"}},{"Key":"0000000010", "Record":{"kokudaka":36,"name":"鍋島勝茂"}}]
root@liva-z:tst# ./queryAsset.sh 0000000003
{"Key":"0000000003", "Record":}

Key=0000000003の伊達政宗レコードが無くなっている。
Futonでも確認。
image.png
やっぱり消えている。
データベースのレコードは削除されているのが確認できた。

#取引履歴を参照する
ブロックチェーンは、取引履歴が1トランザクション/1ブロックの単位でチェーン状に繋がっている(と理解している)。
この取引履歴を参照してみよう。

取引履歴の参照
func (s *SmartContract) getHistoryForKey(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

        fmt.Println("getHistoryForKey:START")
        if len(args) != 1 {
                return shim.Error("Incorrect number of arguments. Expecting 1")
        }

        resultsIterator, err := APIstub.GetHistoryForKey(args[0])
        if err != nil {
                return shim.Error(err.Error())
        }
        defer resultsIterator.Close()

        var buffer bytes.Buffer
        buffer.WriteString("[")

        bFlg := false
        for resultsIterator.HasNext() {
                response, err := resultsIterator.Next()
                if err != nil {
                        return shim.Error(err.Error())
                }

                if bFlg == true {
                        buffer.WriteString(",")
                }
                buffer.WriteString("{\"TxId\":")
                buffer.WriteString("\"")
                buffer.WriteString(response.TxId)
                buffer.WriteString("\"")

                buffer.WriteString(", \"Value\":")
                if response.IsDelete {
                        buffer.WriteString("null")
                } else {
                        buffer.WriteString(string(response.Value))
                }

                buffer.WriteString("}")
                bFlg = true
        }
        buffer.WriteString("]")

        fmt.Println("getHistoryForKey:END")
        return shim.Success(buffer.Bytes())
}

取引履歴を参照する。

root@liva-z:tst# ./getHistoryForKey.sh 0000000003
[{"TxId":"5c3841cae7c5845df9343e3a36665ed08eeae79dcd3d983834da1551970520f1", "Value":{"name":"伊達政宗","kokudaka":62}},{"TxId":"7ad3e93c91d2ff7f01eccb5b534507ef051e29c47ccf836083712aae8c6d6cf5", "Value":{"name":"伊達政宗","kokudaka":70}},{"TxId":"d229e2217dc00bc201557051a7fe300d848e1aab446287a7d6e13181dfbb470b", "Value":null}]

データの登録から更新、削除までの履歴が表示された。
この情報を削除することは、取引履歴を改ざんすることになるから不可能である。
これではシステム上から個人情報や機密情報を完全に削除したとは言えないだろう。

#おわりに
ブロックチェーンで機微な情報を記録する場合は、Private Dataへ記録するか外部RBDとKeyで紐付けて記録すべし。
image.png

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?