package main
import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"fmt"
	"strings"
)
func main() {
	/*
	 *src 암호화 대상
	 *key 암호 key, 16bit면 AES-128, 32bit면 AES-256
	 */
	src := "테스트 평문"
	key := "0123456789abcdef"
	crypted := AesEncrypt(src, key)
	AesDecrypt(crypted, []byte(key))
	Base64URLDecode("")
}
func Base64URLDecode(data string) ([]byte, error) {
	var missing = (4 - len(data)%4) % 4
	data += strings.Repeat("=", missing)
	res, err := base64.URLEncoding.DecodeString(data)
	fmt.Println("  decodebase64urlsafe is :", string(res), err)
	return base64.URLEncoding.DecodeString(data)
}
func Base64UrlSafeEncode(source []byte) string {
	// Base64 Url Safe is the same as Base64 but does not contain '/' and '+' (replaced by '_' and '-') and trailing '=' are removed.
	bytearr := base64.StdEncoding.EncodeToString(source)
	safeurl := strings.Replace(string(bytearr), "/", "_", -1)
	safeurl = strings.Replace(safeurl, "+", "-", -1)
	safeurl = strings.Replace(safeurl, "=", "", -1)
	return safeurl
}
func AesDecrypt(crypted, key []byte) []byte {
	block, err := aes.NewCipher(key)
	if err != nil {
		fmt.Println("err is:", err)
	}
	blockMode := NewECBDecrypter(block)
	origData := make([]byte, len(crypted))
	blockMode.CryptBlocks(origData, crypted)
	origData = PKCS5UnPadding(origData)
	fmt.Println("source is :", origData, string(origData))
	return origData
}
func AesEncrypt(src, key string) []byte {
	block, err := aes.NewCipher([]byte(key))
	if err != nil {
		fmt.Println("key error1", err)
	}
	if src == "" {
		fmt.Println("plain content empty")
	}
	ecb := NewECBEncrypter(block)
	content := []byte(src)
	content = PKCS5Padding(content, block.BlockSize())
	crypted := make([]byte, len(content))
	ecb.CryptBlocks(crypted, content)
	fmt.Println("base64 result:", base64.StdEncoding.EncodeToString(crypted))
	fmt.Println("base64UrlSafe result:", Base64UrlSafeEncode(crypted))
	return crypted
}
/*
PKCS 방식에 따라 아래 함수들은 암, 복호화 함수 내에서 변경
*/
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}
func PKCS5UnPadding(origData []byte) []byte {
	length := len(origData)
	// 去掉最后一个字节 unpadding 次
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}
func pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
	if blocklen <= 0 {
		return nil, fmt.Errorf("invalid blocklen %d", blocklen)
	}
	padlen := 1
	for ((len(data) + padlen) % blocklen) != 0 {
		padlen = padlen + 1
	}
	pad := bytes.Repeat([]byte{byte(padlen)}, padlen)
	return append(data, pad...), nil
}
func pkcs7Unpad(data []byte) ([]byte, error) {
	padlen := int(data[len(data)-1])
	// check padding
	pad := data[len(data)-padlen:]
	for i := 0; i < padlen; i++ {
		if pad[i] != byte(padlen) {
			return nil, fmt.Errorf("invalid padding")
		}
	}
	return data[:len(data)-padlen], nil
}
type ecb struct {
	b         cipher.Block
	blockSize int
}
func newECB(b cipher.Block) *ecb {
	return &ecb{
		b:         b,
		blockSize: b.BlockSize(),
	}
}
type ecbEncrypter ecb
// NewECBEncrypter returns a BlockMode which encrypts in electronic code book
// mode, using the given Block.
func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
	return (*ecbEncrypter)(newECB(b))
}
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
	if len(src)%x.blockSize != 0 {
		panic("crypto/cipher: input not full blocks")
	}
	if len(dst) < len(src) {
		panic("crypto/cipher: output smaller than input")
	}
	for len(src) > 0 {
		x.b.Encrypt(dst, src[:x.blockSize])
		src = src[x.blockSize:]
		dst = dst[x.blockSize:]
	}
}
type ecbDecrypter ecb
// NewECBDecrypter returns a BlockMode which decrypts in electronic code book
// mode, using the given Block.
func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
	return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
	if len(src)%x.blockSize != 0 {
		panic("crypto/cipher: input not full blocks")
	}
	if len(dst) < len(src) {
		panic("crypto/cipher: output smaller than input")
	}
	for len(src) > 0 {
		x.b.Decrypt(dst, src[:x.blockSize])
		src = src[x.blockSize:]
		dst = dst[x.blockSize:]
	}
}


Go에서는 AES의 ECB모드의 보안 취약점때문에 메소드를 지원하지 않습니다. 따라서 위와 같이 직접 구현해야 합니다.

'언어 > GO' 카테고리의 다른 글

[GO] 인터페이스 (Interface)  (0) 2017.02.20
[GO] 파이썬 모듈 실행하기  (0) 2017.02.18
[GO] AES ECB 모드 암호화 (PKCS5, PKCS7)  (0) 2017.02.17
[GO] JSON 사용하기  (0) 2017.02.16
[GO] const와 iota  (0) 2017.02.15
[GO] 구조체 (struct)  (0) 2017.02.15

Go에서 JSON을 사용하려면 encoding/json패키지를 import해야 합니다. 아래는 해당 패키지에서 제공하는 JSON 함수입니다.

  • func Marshal(v interface{}) ([]byte, error): Go 언어 자료형을 JSON 텍스트로 변환
  • func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error): Go 언어 자료형을 JSON 텍스트로 변환하고 사람이 보기 편하도록 들여쓰기를 해줌
  • func Unmarshal(data []byte, v interface{}) error: JSON 텍스트를 Go 언어 자료형으로 변환

key-value 구조 JSON

JSON 직렬화

아래는 맵 형태의 데이터를 JSON 형태로 변환하는 방법입니다.

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	data := make(map[string]interface{}) // 문자열을 키로하고 모든 자료형을 저장할 수 있는 맵 생성

	data["name"] = "kim"
	data["age"] = 25

	doc, _ := json.Marshal(data) // 맵을 JSON 문서로 변환

	fmt.Println(string(doc)) // {"age":25,"name":"kim"}: 문자열로 변환하여 출력
}
 
# 결과
{"age":10,"name":"maria"}

문자열을 키로하고 모든 자료형을 값으로 저장할 수 있는 맵을 할당합니다. 그리고 적절히 데이터를 대입한 뒤 json.Marshal 함수를 사용하여 JSON 문서로 변환합니다. json.Marshal 함수에서 리턴된 값은 []byte 형식이므로 출력할 때는 string 형식으로 변환해줍니다. JSON 문서로 변환했을 때 한 줄로 붙어서 나오면 사람이 읽기 힘듭니다. 따라서 다음과 같이 json.MarshalIndent 함수를 사용하면 사람이 쉽게 읽을 수 있도록 변환할 수 있습니다.


doc, _ := json.MarshalIndent(data, "", "  ")
 
# 결과
{
  "age": 10,
  "name": "maria"
}

첫 번째 매개변수는 JSON 문서로 만들 데이터입니다. 그리고 두 번째 매개변수는 JSON 문서의 첫 칸에 표시할 문자열(Prefix)인데 보통 "" (빈 문자)를 지정합니다. 마지막으로 세 번째 매개변수는 들여쓰기할 문자입니다. 공백 문자나 탭 문자를 사용할 수 있습니다. Prefix는 표시하지 않고, 들여쓰기는 2칸으로 지정하면 다음과 같이 JSON 문서로 변환됩니다.

JSON 역직렬화

아래는 JSON형태를 Go에서 받아 읽는 방법입니다.

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	doc := `
	{
		"name": "kim",
		"age": 25
	}
	`

	var data map[string]interface{} // JSON 문서의 데이터를 저장할 공간을 맵으로 선언
	json.Unmarshal([]byte(doc), &data) // doc를 바이트 슬라이스로 변환하여 넣고, data의 포인터를 넣어줌
	fmt.Println(data["name"], data["age"]) // kim 25: 맵에 키를 지정하여 값을 가져옴
}

구조체를 활용한 복잡한 JSON 표현

위 방법은 아주 간단한 1단계인 key-value 구조였습니다. 이번엔 구조체를 활용해 더 복잡한 구조를 표현하겠습니다.

JSON 역직렬화

package main

import (
	"encoding/json"
	"fmt"
)

type Author struct {
	Name  string
	Email string
}

type Comment struct {
	Id      uint64
	Author  Author // Author 구조체
	Content string
}

type Article struct {
	Id         uint64
	Title      string
	Author     Author    // Author 구조체
	Content    string
	Recommends []string  // 문자열 배열
	Comments   []Comment // Comment 구조체 배열
}

func main() {
	doc := `
	[{
		"Id": 1,
		"Title": "Hello, world!",
		"Author": {
			"Name": "Maria",
			"Email": "maria@example.com"
		},
		"Content": "Hello~",
		"Recommends": [
			"John",
			"Andrew"
		],
		"Comments": [{
			"id": 1,
			"Author": {
				"Name": "Andrew",
				"Email": "andrew@hello.com"
			},
			"Content": "Hello Maria"
		}]
	}]
	`

	var data []Article // JSON 문서의 데이터를 저장할 구조체 슬라이스 선언

	json.Unmarshal([]byte(doc), &data) // doc의 내용을 변환하여 data에 저장

	fmt.Println(data) // [{1 Hello, world! {Maria maria@exa... (생략)
}

JSON 직렬화

데이터를 JSON 형태로 변환해보겠습니다.

package main

import (
	"encoding/json"
	"fmt"
)

type Author struct {
	Name  string
	Email string
}

type Comment struct {
	Id      uint64
	Author  Author // Author 구조체
	Content string
}

type Article struct {
	Id         uint64
	Title      string
	Author     Author    // Author 구조체
	Content    string
	Recommends []string  // 문자열 배열
	Comments   []Comment // Comment 구조체 배열
}

func main() {
	data := make([]Article, 1) // 값을 저장할 구조체 슬라이스 생성

	data[0].Id = 1
	data[0].Title = "Hello, world!"
	data[0].Author.Name = "Maria"
	data[0].Author.Email = "maria@example.com"
	data[0].Content = "Hello~"
	data[0].Recommends = []string{"John", "Andrew"}
	data[0].Comments = make([]Comment, 1)
	data[0].Comments[0].Id = 1
	data[0].Comments[0].Author.Name = "Andrew"
	data[0].Comments[0].Author.Email = "andrew@hello.com"
	data[0].Comments[0].Content = "Hello Maria"

	doc, _ := json.Marshal(data) // data를 JSON 문서로 변환

	fmt.Println(string(doc)) // [{"Id":1,"Title":"Hello, world!","Au... (생략)
}


패키지에서 함수(변수, 상수)를 외부에 노출할 때 이름의 첫 글자를 대문자로 만드는 것처럼 구조체도 같은 규칙으로 동작합니다. 즉 JSON 등으로 외부에 노출할 때는 구조체 필드의 첫 글자는 대문자로 만듭니다. 따라서 구조체 필드의 첫 글자를 소문자로 만들면 JSON 문서에 해당 필드는 포함되지 않습니다.

구조체 필드가 대문자로 시작하면 JSON 문서 안의 키도 대문자로 시작하게 됩니다. 여기서 JSON 문서 안의 키를 소문자로 시작하고 싶다면 다음과 같이 구조체 필드에 태그를 지정해줍니다.

// 필드명 자료형 `json:”키”`


type 구조체명 struct {
	필드명 자료형 `json:"키"`
}


type Author struct {
	Name  string `json:"name"` // 구조체 필드에 태그 지정
	Email string `json:"email"`
}

type Comment struct {
	Id      uint64 `json:"id"`
	Author  Author `json:"author"`
	Content string `json:"content"`
}

type Article struct {
	Id         uint64    `json:"id"`
	Title      string    `json:"title"`
	Author     Author    `json:"author"`
	Content    string    `json:"content"`
	Recommends []string  `json:"recommends"`
	Comments   []Comment `json:"comments"`
}

태그는 문자열 형태이며 문자열 안에 " " (따옴표)가 포함되므로 ` ` (백쿼트)로 감싸줍니다. 그리고 JSON 문서이므로 `json:"키"` 형식으로 키 이름을 직접 지정합니다. 여기서 키 이름은 구조체 필드와 같을 필요는 없습니다.


태그를 지정한 구조체로 JSON 문서를 만들면 다음과 같이 키를 소문자로 만들 수 있습니다.

[
  {
    "id": 1,
    "title": "Hello, world!",
    "author": {
      "name": "Maria",
      "email": "maria@example.com"
    },
    "content": "Hello~",
    "recommends": [
      "John",
      "Andrew"
    ],
    "comments": [
      {
        "id": 1,
        "author": {
          "name": "Andrew",
          "email": "andrew@hello.com"
        },
        "content": "Hello Maria"
      }
    ]
  }
]


구조체 태그는 JSON 문서를 만들거나, 타입 확인, 리플렉션(reflect 패키지)에서 사용할 수 있습니다


'언어 > GO' 카테고리의 다른 글

[GO] 파이썬 모듈 실행하기  (0) 2017.02.18
[GO] AES ECB 모드 암호화 (PKCS5, PKCS7)  (0) 2017.02.17
[GO] JSON 사용하기  (0) 2017.02.16
[GO] const와 iota  (0) 2017.02.15
[GO] 구조체 (struct)  (0) 2017.02.15
[GO] 메서드(Method)  (0) 2017.02.15

아래와 같이 status 변수에 상태를 정의한다고 가정합니다.

type status int
 
const UNKNOWN status = 0
const TODO status = 1
const DONE status = 2


이러한 세 상수는 서로 연관이 있으므로 묶어서 쓸 수 있습니다.

const (
	UNKNOWN status = 0
	TODO    status = 1
	DONE    status = 2
)


위와 같이 순서대로 수를 붙인다면 아래와 같이 iota를 사용할 수 있습니다. 또한 자료형도 첫 줄과 동일하게 묶입니다.

const (
	UNKNOWN status = iota
	TODO
	DONE
)


iota는 순서대로 0, 1, 2 붙이는 것 이욍도 사용방법이 많습니다.

const (
	_           = iota // 첫 줄 무시
	KB ByteSize = 1 << (10 * iota)
	MB
	GB
	TB
	PB
	EB
	ZB
	YB
)

iota가 0부터 시작하는데 첫 번째 값인 0은 버립니다. 다음 iota는 1이 되는데 1 << (10 * iota)를 사용했습니다. 다시말해 1 << (10 * 1)인데 << 연산자는 비트를 왼쪽으로 이동시키는 연산자로 1을 왼쪽으로 10번 이동시키겠다는 것으로 사실상 2의 10승인 1024가 됩니다. 즉, KB는 1024가 됩니다. 그 다음 MB는 1 << ( 10 * 2)이므로 2의 20승이 됩니다.


위와 같이 첫 줄을 버려도 되지만 아래와 같이 첫 줄을 버리지 않고 동일한 기능을 하도록 구현할 수 있습니다.

const (
	KB ByteSize = 1 << (10 * (1 + iota))
	MB
	GB
	TB
	PB
	EB
	ZB
	YB
)


'언어 > GO' 카테고리의 다른 글

[GO] AES ECB 모드 암호화 (PKCS5, PKCS7)  (0) 2017.02.17
[GO] JSON 사용하기  (0) 2017.02.16
[GO] const와 iota  (0) 2017.02.15
[GO] 구조체 (struct)  (0) 2017.02.15
[GO] 메서드(Method)  (0) 2017.02.15
[GO] 명명된 함수형 (Named Function)  (0) 2017.02.08

구조체는 필드들을 묶어놓은 것으로 다른 언어들의 구조체, 클래스, 레코드 등과 비슷한 역할을 합니다. 구조체를 이용하면 더 복잡한 자료형을 정의할 수 있습니다. 자료를 네트워크를 통하여 전송하거나 파일에 저장하고 불러오는 경우에는 직렬화 및 역직렬화 기법을 활용해야 합니다. JSON은 이런 직렬화 및 역직렬화 형식 중에 하나로, 복잡한 구조체를 어떻게 JSON 형태로 직렬화하고 역직렬화할 수 있는지도 확인해봅니다.

구조체란?

필드들의 모음 혹은 묶음을 구조체라고 합니다. 명명된 구성요소들을 필드라고 합니다. 배열이 서로 같은 자료형의 자료들을 묶어놓은 것이라면 구조체는 서로 다른 자료형의 자료들도 묶을 수 있습니다.

사용법

var task = struct {
        title string
        done bool
        due *time.Time
    }{"laundry", false, nil}

구조체는 위와 같이 선언과 동시에 값을 넣을 수 있습니다. task란 구조체는 title이란 이름의 string 타입, done 이름의 bool 타입, due 이름의 *time.Time 타입이 존재하고 title에는 "laundry" 값을, done 에는 false 값을 due 에는 nil 값을 선언했습니다.


아래에서 위 구조체를 분해해가면서 설명하겠습니다.

type Task struct {
        title string
        done bool
        due *time.Time
    }

자료형은 struct {...} 부분입니다. 위는 명명된 구조체로 Task란 구조체를 타입으로 선언했습니다.


아래와 같이 Task 변수를 선언할 수 있습니다.

var tempTask Task


마지막을 값을 담아서 정의해보겠습니다.

var tempTask = Task{"laundry", false, nil}


아래와 같이 원하는 필드만 값을 넣을 수 있습니다. 넣지 않은 필드들은 기본값으로 설정됩니다. 기본값은 정수는 0, bool은 false, 문자열은 빈 문자열 등입니다.

var tempTask = Task{title: "laundry"}
var tempTask = Task{title: "laundry", done: true}


만약 한 줄이 아닌 여러 줄로 표현하고 싶다면 아래와 같이 표현합니다. 주의할 점은 필드 마지막에 쉼표(,)를 붙여야 합니다. 쉼표가 없을 경우, 세미콜론(;)이 붙은 것으로 인식되어 오류가 발생합니다.

var tempTask = Task{
        title: "laundry", 
        done: true,
    }


'언어 > GO' 카테고리의 다른 글

[GO] JSON 사용하기  (0) 2017.02.16
[GO] const와 iota  (0) 2017.02.15
[GO] 구조체 (struct)  (0) 2017.02.15
[GO] 메서드(Method)  (0) 2017.02.15
[GO] 명명된 함수형 (Named Function)  (0) 2017.02.08
[GO] 명명된 자료형 (Named Type)  (0) 2017.02.08

Go 언어는 객체지향 프로그래밍(OOP)을 고유의 방식으로 지원합니다. 타 언어의 OOP의 클래스가 필드와 메서드를 함께 갖는 것과 달리 Go 언어에서는 struct가 필드만을 가지며, 메서드는 별도로 분리되어 정의됩니다.

Go 메서드는 특별한 형태의 func 함수입니다. 메서드는 함수 정의에서 func 키워드와 함수명 사이에 "그 함수가 어떤 struct를 위한 메서드인지"를 표시하게 됩니다. 흔히 receiver(리시버)로 불리는 이 부분은 메서드가 속한 struct 타입과 struct 변수명을 지정하는데, struct 변수명은 함수 내에서 마치 입력 파라미터처럼 사용됩니다. 예를 들어, 아래 예제는 Rect라는 struct를 정의하고 area() 라는 메서드를 정의하고 있습니다. func와 area() 사이에 Rect 타입의 r 이 정의되고 이를 함수 본문에서 사용하고 있습니다. 메서드가 선언된 이후에는 Rect 구조체의 객체는 rect.area() 문장처럼 area() 메소드를 struct 객체로부터 직접 호출할 수 있습니다. 

모양이 함수(Function)과 유사하지만 동작은 전혀 다르니 유의해야 합니다.

package main
 
//Rect - struct 정의
type Rect struct {
    width, height int
}
 
//Rect의 area() 메소드
func (r Rect) area() int {
    return r.width * r.height   
}
 
func main() {
    rect := Rect{10, 20}
    area := rect.area() //메서드 호출
    println(area)
}
 
// 결과
// 200

value receiver VS pointer receiver

Value receiver는 struct의 데이터를 복사(copy)하여 전달하며, 포인터 receiver는 struct의 포인터만을 전달합니다. Value receiver의 경우 만약 메서드 내에서 그 struct의 필드값이 변경되더라도 호출자의 데이타는 변경되지 않는 반면, 포인터 receiver는 메서드 내의 필드값 변경이 그대로 호출자에서 반영됩니다.

위의 Rect.area() 메서드는 Value receiver를 표현한 것으로 만약 area() 메서드 내에서 width나 height가 변경되더라도 main() 함수의 rect 구조체의 필드값에는 변화가 없습니다. 하지만, 아래와 같이 이를 포인터 receiver로 변경한다면, 메서드 내 r.width++ 필드 변경분이 main() 함수에서도 반영되기 때문에 출력값이 11, 220 이 됩니다.

package main
 
//Rect - struct 정의
type Rect struct {
    width, height int
}
 
// 포인터 Receiver
func (r *Rect) area2() int {
    r.width++
    return r.width * r.height
}
 
func main() {
    rect := Rect{10, 20}
    area := rect.area2() //메서드 호출
    println(rect.width, area) // 11 220 출력
}
 
// 결과
// 11 220


'언어 > GO' 카테고리의 다른 글

[GO] const와 iota  (0) 2017.02.15
[GO] 구조체 (struct)  (0) 2017.02.15
[GO] 메서드(Method)  (0) 2017.02.15
[GO] 명명된 함수형 (Named Function)  (0) 2017.02.08
[GO] 명명된 자료형 (Named Type)  (0) 2017.02.08
[GO] 클로저를 사용해 생성기(generator) 생성  (0) 2017.02.08

명명된 자료형과 마찬가지로 함수 또한 사용자가 정의할 수 있습니다.


type BinOp func(int, int) int

두 정수를 넘겨받아 정수 하나를 반환하는 함수형을 BinOp형으로 정의하였습니다. 명명된 함수형도 자료형 검사를 실행합니다.


func Test(f BinOp) {
	fmt.Println(f(3, 4))
}

위의 함수를 호출할 때, 아래와 같이 호출한다고 가정합니다.


Test(func (a, b int) int {
	return a+b
})

Test는 BinOp형을 인자로 받는데, 호출 시에는 그냥 정수 둘을 받ㄱ 정수 하나를 반환하는 함수를 넘겨줬습니다. 위의 코드는 컴파일 에러를 발생하지 않습니다. func(a, b int) int 자료형이 명명되지 않은 자료형이기 때문입니다. 양쪽 모두 명명된 자료형이 아니면 서로 간에 호환됩니다.

+ Random Posts