본문 바로가기
Study/Go 언어

[Go언어] 암호화, 프로토콜

by Jamie Lim 2020. 8. 17.
사용 교재 : 가장 빨리 만나는 Go언어
5주차 : UNIT 52 ~ 60

[UNIT 52] 압축 사용하기

 - 압축 알고리즘은 파일, 네트워크 패킷을 압축해 용량을 줄이고 압축된 파일, 네트워크 패킷을 해제할 때 사용

 

 

 - compress/gzip 패키지에서 제공하는 압축 함수

   > func NewReader(r io.Reader) (*Reader, error) : io.Reader 인터페이스로 io.Reader 인터페이스를 따르는 압축 해제 인스턴스 생성

   > func NewWriter(w io.Writer) *Writer : io.Writer 인터페이스로 io.Writer 인터페이스를 따르는 압축 인스턴스 생성

 

 

 - io/ioutil 패키지에서 제공하는 데이터 읽기 함수

   > func ReadAll(r io.Reader) ([]byte, error) : io.Reader를 끝까지 읽어 바이트 슬라이스로 리턴한다

 

 

 - gzip 알고리즘을 사용해 데이터를 압축한 뒤 파일로 저장하기

 

  

 

 - 압축한 파일 압축 해제해 읽기

 

     

 

 - io.Reader, io.Writer 인터페이스를 따르므로 같은 방법으로 압축 및 해제 가능

   > compress/bzip2 : func NewReader(r io.Reader) io.Reader

   > compress/zlib

         : func NweReader(r io.Reader) (io.ReadCloser, error)

          func NewWriter(w io.Writer) *Writer

   > compress/flate

         : func NewReader(r io.Reader) io.ReadCloser

          func NewWriter(w io.Writer, level int) (*Writer, error)

   > compress/lzw

         : func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser

          func NewWriter(w io.Writer, order Order, litWidth int) io.WriteCloser

 

 


[UNIT 53] 암호화 사용하기

 - 해시, 대칭키 알고리즘, 공개키 알고리즘을 비롯해 여러 암호화 알고리즘을 패키지로 제공한다

 - 암호화 알고리즘은 파일을 암호화하거나 네트워크로 데이터를 주고받을 때 데이터를 보호하기 위해 사용한다

 

 

 - 암호화 알고리즘

   > 해시 알고리즘 : MD5, SHA1, SHA256, SHA512 등이 있고 데이터에서 고유한 해시 값을 추출해낸다. 이 해시 값으로 데이터를 만들어낼 수 없다. 단방향 암호화 알고리즘이라고도 하며 패스워드를 저장할 때 사용한다

   > 대칭키 알고리즘 : AES, DES, RC4 등이 있고 암호화하는 키와 복호화하는 키가 동일하다

   > 공개키 알고리즘 : RSA가 대표적이며 암호화하는 키와 복호화하는 키가 다르다. 암호화하는 키는 공개키라 하여 외부에 공개하고, 복호화하는 키는 비밀키라 하여 외부에 노출하지 않는다. 공개키로는 비밀키를 만들어 내기 힘들게 설계되어 있다.

 

 1. 해시 알고리즘 사용하기

   - crypto/sha512 패키지에서 제공하는 해시 알고리즘 함수

    > func New() hash.Hash : SHA512 해시 인스턴스 생성

    > func Sum512(data []byte) [Size]byte : SHA512 해시를 계산하여 리턴

    > func (d *digest) Write(p []byte) (nn int, err error) : 해시 인스턴스에 데이터 추가

    > func (d0 *digest) Sum(in []byte) []byte : 해시 인스턴스에 저장된 데이터의 SHA512 해시 값 추출

 

   - SHA512 알고리즘을 사용해 데이터에서 해시 값을 추출해보기

 

 

 

 2. AES 대칭키 알고리즘 사용하기

   - crypto/aes 패키지에서 제공하는 대칭키 알고리즘 함수

    > func NewCipher(ket []byte) (cipher.Block, error) : 대칭키 암호화 블록 생성

    > func (c *aesCipher) Encrypt(dst, src []byte) : 평문을 AES 알고리즘으로 암호화

    > func (c *aesChipher) Decrypt(dst, src []byte) : AES 알고리즘으로 암호화된 데이터를 평문으로 복호화

 

 

   - AES 대칭키 알고리즘을 사용해 데이터를 암호화하고 복호화하기

    > 실무에서는 아래 예제보다 데이터의 크기가 훨씬 크다. 그렇기에 긴 데이터들은 16바이트씩 잘라 암호화하면 된다. 하지만 데이터를 잘라 암호화하면 보안에 취약해진다. (ECP 방식)

     

 

   - 긴 데이터를 안전하게 암호화하기 위해 대칭키 알고리즘은 다양한 운용 방식을 제공한다 (CBC 방식)

 

 

   - crypto/cipher 패키지에서 제공하는 암호화 운용 모드 함수

    > func NewCBCEncrypter(b Block, iv []byte) BlockMode : 암호화 블록과 초기화 벡터로 암호화 블록 모드 인스턴스 생성

    > func (x *cbcEncrypter) CryptBlocks(dst, src []byte) : 암호화 블록 모드 인스턴스로 암호화

    > func NewCBCDEcrypter(b Block. Iv []byte) BlockMode : 암호화 블록과 초기화 벡터로 복호화 블록 모드 인스턴스 생성

    > func (x *cbcDectypter) CryptBlocks(dst, src []byte) : 복호화 블록 모드 인스턴스로 복호화

 

 

   - io 패키지에서 제공하는 읽기 함수

    > func ReadFull(r Reader, buf []byte) (n int, err error) : io.Reader에서 buf의 길이만큼 데이터를 읽음

 

 

   - 긴 데이터 암호화시키기

    > 블록 암호화는 암호화시 데이터의 길이가 블록 크기의 배수여야 한다

    > CBC 운용 방식은 초기화 벡터를 첫 블록에 배치하고 각 블록은 이전 블록의 암호화 결과와 XOR한다

      → 초기화 벡터는 암호화할 때마다 매번 다른 값으로 생성한다

 

소스 코드
실행 결과

 

 3. RSA 공개키 알고리즘 사용하기

   - 대칭키 알고리즘은 암호 키가 유출되면 위험하기 때문에 대칭키 알고리즘의 암호 키를 공개키 알고리즘으로 암호화한다

 

 

   - crypto/rsa 패키지에서 제공하는 공개키 알고리즘 함수

    > func GenerateKey(random io.Reader, bits int) (priv *PrivateKey, err error) : 개인키와 공개키 생성

    > func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) (out []byte, err error) : 평문을 공개키로 암호화

    > func DecryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) (out []byte, err error) : 암호화된 데이터를 개인키로 복호화

 

   - RSA 공개키 알고리즘을 사용해 암호화와 복호화하기

 

소스 코드
실행 결과

 

 

   - 공개키 알고리즘은 암호화할 때 공개키를 사용하고 복호화할 때는 개인키를 사용한다

   - 개인키는 외부에 노출하지 않고 공개키로는 개인키를 추출하기 어렵기 때문에 데이터를 안전하게 암호화할 수 있다

 

   - crypto/rsa 패키지에서 제공하는 서명, 인증 함수

    > func SingPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) (s []byte, err error) : 개인키로 서명

    > func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) (err error) : 공개키로 서명 검증

 

 

   - crypto/md5 패키지에서 제공하는 해시 함수

    > func New() hash.Hash : MD5 해시 인스턴스 생성

    > func (d *digest) Write(p []byte) (nn int, err error) : 해시 인스턴스에 데이터 추가

    > func (d0 *digest) Sum(in []byte) []byte : 해시 인스턴스에 저장된 데이터의 MD5 해시 값 추출

 

  

 

 - 메시지, 메시지에 대한 해시 값, 서명, 공개키는 모두 공개된 정보이고 이를 이용해 메시지가 변조되지 않고 올바른 사람에게서 왔는지 검증할 수 있다

   > 메시지에 대한 해시 값으로 메시지가 변조되었는지 확인할 수 있음

   > 서명 값으로 메시지가 올바른 사람한테서 왔는지 확인할 수 있다 -> 서명할 수 있는 사람은 개인키를 가진 사람뿐이다.

 

 

 


[UNIT 54] 정렬 활용하기

 - sort 패키지에서 제공하는 정렬 함수와 타입

   > func Sort(data Interface) : 데이터를 오름차순으로 정렬

   > func Reverse(data Interface) Interface : 데이터를 내림차순으로 정렬

   > func InSlice []int : int 정렬 인터페이스

   > type Float64Slice []float64 : float64 정렬 인터페이스

   > type StringSlice []string : string 정렬 인터페이스

 

 

 - 정수, 실수, 문자열 슬라이스 용소 정렬

 

 

 

 - 데이터가 담긴 슬라이스를 sort.Sort 함수에 넣으면 정렬된다 (기본으로 오름차순)

 - 내림차순 정렬을 하면 sort.Reverse 함수 사용하면 된다

 - sort.Sort 함수에 슬라이스를 넣을 땐 다음 같은 자료형에 맞게 정렬 인터페이스를 사용한다

 - 정수, 실수, 문자열과 같은 기본 자료형은 다음과 같이 슬라이스를 바로 넣을 수 있는 함수를 제공한다

 

자료형 정렬 인터페이스
int sort.IntSlice
float64 Sort.Float64Slice
string sort.StringSlice

 

 1. 정렬 인터페이스를 이용해 구조체 정렬하기

   - 정렬 인터페이스의 정의

 

type Interface interface {

    Len() int          //  데이터의 길이를 구한다

    Less(i, j, int) bool  // 대소관계를 판단하고 i가 작으면 true를 리턴하도록 구현한다

    Swap(i, j int)      // Less 메서드에서 true가 나오면 두 데이터의 위치를 바꾼다

}

 

 

  - 정렬 인터페이스를 구현해 구조체가 담긴 슬라이스 정렬하기 (이름, 점수순으로 정렬) 

  

  

 

 2. 정렬 키로 정렬하기

   - 정렬 키 함수를 사용해 정렬하기

   

소스 코드
실행 결과

    

 

   - 하나의 데이터 타입을 여러 인터페이스로 바꿔가며 OOP를 구현하는데 하수의 타입을 바꾸는 방식을 기본적으로 표현하기 위해 다음과 같이 할 수 있다

 

Var b By = name

b.Sort(s)

fmt.Println(s)

 


[UNIT 55] 컨테이너 사용하기

 - 기본 자료구조를 패키지로 제공하므로 편리하게 데이터를 다룰 수 있다

 

 

 - 자료구조

   > 연결 리스트 : 각 노드를 한 줄로 연결한 자료구조

   > : 이진 트리를 활용한 자료구조. 부모 노드에서 자식 노드가 대소관계를 이루며 정렬이 되는 것이 특징

   > : 각 노드가 원형으로 연결된 자료구조

 

 1. 연결 리스트 사용하기

   - container/list 패키지에서 제공하는 연결 리스트 함수

    > func New() *List : 연결 리스트 생성

    > func (l *List) PushBack(v interface{ }) *Element : 연결 리스트의 맨 뒤에 데이터 추가

    > func (l *List) Front( ) *Element : 연결 리스트의 맨 앞 데이터를 가져온다

    > func (l *List) Back( ) *Element : 연결 리스트의 맨 뒤 데이터를 가져온다

 

 

   - 연결 리스트를 생성해 데이터를 넣고 각 노드 순회하기

   

 

 

   - Go 언어의 연결 리스트는 이중 연결 리스트로 앞, 뒤 양쪽 방향으로 순회 가능하다

   - 연결 리스트에서 사용할 수 있는 함수

 

함수 설명
PushBack 맨 뒤에 노드를 추가한다
PushFront 맨 앞에 노드를 추가한다
PushBackList 맨 뒤에 다른 리스트를 붙인다
PushFrontList 맨 앞에 다른 리스트를 붙인다
InsertAfter 특정 노드 뒤에 노드를 추가한다
InsertBefore 특정 노드 앞에 노드를 추가한다
MoveAfter 노드를 특정 노드 뒤로 옮긴다
MoveBefore 노드를 특정 노드 앞으로 옮긴다
MoveToBack 노드를 맨 뒤로 옮긴다
MoveToFront 노드를 맨 앞으로 옮긴다
Len 리스트의 노드 개수(길이)를 구한다
Remove 특정 노드를 삭제한다

    

 

   - Front().Value, Back().Value로 리스트의 맨 앞, 맨 뒤 노드에서 값을 가져올 수 있다

    

연결 리스트 구조

 

 2. 힙 사용하기

   - container/heap 함수에서 제공하는 힙 함수

    > func Init(h Interface) : 힙 초기화

    > func Push(h Interface, x interface{}) : 힙에 데이터 추가                                                                                                                                                                     

 

   - 힙 사용하기

    > 최대 힙 : 부모 노드의 값이 자식 노드의 값보다 큰 힙

    > 최소 힙 : 부모 노드의 값이 자식 노드의 값보다 작은 힙

    > 예제는 최소 힙으로 구현함

   

소스 코드
실행 결과

  

 

   - 실행 과정

 

최소 힙 정렬 과정

 

 3. 링 사용하기

   - container/ring 패키지에서 제공하는 링 함수

    > func New(n int) *Ring : 링 생성

    > func (r *Ring) Do(f func(interface{})) : 링의 모든 노드 순회

    > func (r *Ring) Move(n int) *Ring : 링을 회전시켰고 매개변수로 양수를 넣으면 시계 방향, 음수를 넣으면 반 시계 방향으로 회정

 

 

   - 링 사용하기

   

 

 

   - 실행과정

  

링 회전 과정

 


[UNIT 56] TCP 프로토콜 사용하기

 - HTTP, FTP, SMTP, DHCP 프로토콜 등 인터넷에서 흔히 사용하는 프로토콜 모두 TCP 프로토콜을 기반으로 구현한다

 

 1. 서버 작성하기

   - net 패키지에서 제공하는 TCP 함수

    > func Listen(net, laddr string) (Listener, error) : 프로토콜, IP 주소, 포트 번호를 설정하여 네트워크 연결을 대기한다

    > func (l *TCPListener) Accept() (Conn, error) : 클라이언트가 연결되면 TCP 연결을 리턴

    > func (l *TCPListener) Close() error : TCP 연결 대기를 닫음

    > func (c *TCPConn) Read(b []byte) (int, error) : 받은 데이터를 읽음

    > func (c *TCPConn) Write(b []byte) (int, error) : 데이터를 보냄

    > func (c *TCPConn) Close() error : TCP 연결 닫음

 

 

   - TCP 서버 만들기

    > 클라이언트에서 보낸 메시지를 다시 응답해주기

    > GOPATH/src/tcpserver/tcpserver.go 파일로 저장

  

    

 2. 클라이언트 작성하기

   - net 패키지에서 제공하는 TCP 함수

    > func Dial(network, address string) (Conn, error) : 프로토콜, IP 주소, 포트 번호를 설정하여 서버에 연결

    > func (c *TCPConn) Close() error : TCP 연결을 닫음

    > func (c *TCPConn) Read(b []byte) (int, error) : 받은 데이터 읽기

    > func (c *TCPConn) Write(b []byte) (int, error) : 데이터 보내기

 

 

   - 클라이언트 작성하기

  

   

 

 - 실행하기

   > 명령 프롬프트에서 go run 파일명.go를 통해 서버와 클라이언트를 실행시킨다

   > 1초에 한 번씩 Hello n이 출력됨

   

   > 서버

 

   > 클라이언트

 


[UNIT 57] RPC 프로토콜 사용하기

 - RPC : Remote Procedure Call, 원격 프로시저 호출로 원격에서 함수를 실행하는 기술이다

 

 1. 서버 작성하기

   - net/rpc 패키지에서 제공하는 RPC 함수

    > func Register(rcvr interface{}) error : RPC로 사용할 함수 등록

 

 

   - 원격에서 덧셈 함수 호출해 두 수 더하기

  

 

 2. 클라이언트 작성하기

   - net/rpc 패키지에서 제공하는 RPC함수

    > func Dial(network, address string) (*Client, error) : 프로토콜, IP 주소, 포트번호를 설정해 RPC 서버에 연결

    > func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error : RPC 서버의 함수를 호출

    > func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call : RPC 서버의 함수를 고루틴으로 호출

 

 

   - 클라이언트

  

 

 

 - 실행하기

   > RPC 서버

 

 

   > 클라이언트

   


[UNIT 58] HTTP 서버 사용하기

 - net/http 패키지에서 제공하는 HTTP 함수

> func ListenAndServe(addr string, handler Handler) error : HTTP 연결을 받고, 요청에 응답

> func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) : 경로별로 요청을 처리할 핸들러 함수를 등록

> func Handle(pattern string, handler Handler) : 경로별로 요청을 처리할 핸들러 등록

> func FileServer(root FileSystem) Handler : 파일 서버 핸들러

> func StripPrefix(prefix string, h Handler) Handler : HTTP 요청 경로에서 특정 문자열을 제거한다

  * /assets/hello.js -> hello.js

> func NewServeMux() *ServeMux : HTTP 요청 멀티플렉서 인스턴스 생성

 

 

 - Hello world! 출력하기

 

 

 

 - 실행하기

 

 

 - http.ListenAndServe 함수에 핸들러를 넣어 웹 서버 실행

 

 

 - 실행하기

  

 

 - CSS 색상을 설정하고 경고 창을 출력하는 웹 서버 만들기

 

 


[UNIT 59] 명령줄 옵션 사용하기

 - os 패키지에서 제공하는 변수

   > var Args []string : 명령줄에서 설정한 옵션

 

 

 - 설정 옵션 가져오기

 

 

 

 - 실행 파일을 아래와 같이 실행하면 실행 파일과 설정한 옵션이 출력된다

  > 첫 번째 요소는 현재 실행 파일의 경로이고 두 번째 요소부터는 옵션이다

 

명령줄 옵션과 os.Args

 

 

 - flag 패키지에서 제공하는 함수 (명령줄 옵션을 편리하게 사용할 수 이게 해줌)

   > func String(name string, value string, usage string) *string : 명령줄 옵션을 받고 문자열로 저장

   > func Int(name string, value int, usage string) *int : 명령줄 옵션을 받은 뒤 정수로 저장   

   > func Float64(name string, value float64, usage string) *float64 : 명령줄 옵션을 받은 뒤 실수로 저장

   > func Bool(name string, value bool, usage string) *bool : 명령줄 옵션을 받은 뒤 불로 저장   

   > func Parse() : 명령줄 옵션의 내용을 각 자료형별로 분석

   > func NFLag() int : 명령줄 옵션의 개수 리턴

   > var Usage = func() {} : 명령줄 옵션의 기본 사용법 출력

 

 

 - flag를 사용해 옵션 사용법 출력하기

 

 

 

 - 파일 실행하기

   > 컴파일 한 뒤 실행하면 터미널에서 실행하면 아래와 같은 결과가 출력된다

 

   > 아무런 옵션을 설정하지 않으면 옵션 사용법이 출력된다

  

 


[UNIT 60] 에러 처리하기

 - fmt 패키지에서 제공하는 에러 출력 함수

   > func Errorf(format string, a …interface{}) error : 형식을 지정해 error 값을 만들었다

 

 

 - log 패키지에서 제공하는 에러 출력 함수

   > func Fatal(v …interface{}) : 에러 로그를 출력해 프로그램을 완전히 종료

   > func Panic(v …interface{}) : 시간과 에러 문자열을 출력한 뒤 패닉을 발생시킴

   > func Print(v …interface{}) : 시간과 메어 메시지를 출력하며 프로그램을 종료하지 않음

 

 

 - 1이 아닐 때 에러 발생시키기

     

소스 코드
실행 결과

 

 

 - log.Panic 함수 사용 (프로그램을 정상 종료하지 않고 런타임 에러 발생시키기)

   > 에러 문자열이 출력된 뒤 런타임 패닉이 발생해 콜스택이 출력된다

 

   

   > recover 함수로 복구를 사용해 프로그램을 종료하지 않고 넘어갈 수 있다

   > 프로그램이 종료되지 않고 log.Panic 함수에서 설정한 에러 메시지만 출력됨

 

  

 

   > 실행 과정

 

 1. 에러 타입 만들기

   - 에러 타입은 에러를 더 확장해 자세한 에러 내용을 저장하기 위해 사용한다

 

 

   - 에러 타입 만들기

  

     

 

 - 다음과 같이 작성하면 error 타입으로 리턴된 HelloOneError 구조체 필드에 접근할 수 있게 된다

 

s, err = helloOne(2)

        if err != nil {

               fmt.Println(err.(HelloOneError).value)

          }

 

댓글