암호화와 복호화, 양방향 알고리즘(공개키, 비공개키)

|

개인적인 연습 내용을 정리한 글입니다.
더 좋은 방법이 있거나, 잘못된 부분이 있으면 편하게 의견 주세요. :)


암호란?

평문을 암호문으로 변환하거나 암호문을 평문으로 변환하는 기술을 의미합니다.

  • 평문: 해독 가능한 형태의 텍스트. 예: “123456”
  • 암호문: 해독 불가능한 형태의 텍스트. 예: “aWfwlknr!”

암호화와 복호화

  • 암호화: 평문을 암호문으로 변환하는 과정
    • 예: 평문(“123456”) -> 암호화 -> 암호문(“aEerwlrkn!”)
  • 복호화: 암호문을 평문으로 변환하는 과정
    • 예: 암호문(“aQwqlen!”) -> 복호화 -> 평문(“123456”)

암호화의 종류

암호화 종류에는 단방향 암호화와 양방향 암호화가 있습니다.

  • 단방향 암호화: 암호화 후 복호화를 할 수 없는 것이 특징입니다.
    • 예: 사용자 비밀번호. 사용자가 입력한 비밀번호를 암호화하여 DB에 저장이 될 때, 모든 접근자(개발자 포함)는 암호화 된 코드를 다시 평문으로 볼 수 없습니다. 이는 해킹이 되어도 복호화가 매우 힘든것이 특징입니다.
    • 해시 방식이 대표적
    • 해시 함수: 해시함수는 임의의 길이의 데이터를 입력받아 일정한 길이의 비트열로 반환 시켜주는 함수로 입력값의 길이가 달라도 출력값은 언제나 고정된 길이로 반환되며 동일한 값이 입력되면 언제나 동일한 출력값을 보장하는 함수를 의미합니다.
  • 양방향 암호화: 암호화와 복호화 모두 가능한 것이 특징입니다.
    • 예: 이메일, 번호 또는 전자 서명. 이와 같이 재사용성이 있는 정보는 암호화, 복호화 모두 이루어져야 합니다.
    • 대칭키, 비대칭키 방식이 대표적

양방향 알고리즘

  • 대칭형(비밀키 암호): 암호화, 복호화 시 모두 동일한 키를 사용
  • 비대칭형(공개키 암호): 암호화, 복호화에 서로 다른 키를 사용

1. 대칭형(비공개키 암호) 알고리즘

암호화, 복호화에 서로 동일한 키가 사용되는 암호화 방식으로 키를 비공개하는 것이 특징입니다. 속도가 빠르다는 장점이 존재하지만 키 배송 위험성이 존재하여 송신 측에서 수신측에 암호키를 전달하는 과정에서 키가 노출될 우려가 있다는 단점이 존재합니다. 대표적으로는 AES가 있습니다.

대칭키(비공개키)는 사용하는 키와 복호화 할때 사용하는 키가 동일한 암호화 기법으로, 대게 암호화 알고리즘이라 하면 위 알고리즘을 가리킵니다. 현재 가장 보편적으로 쓰이는 암호화 방식은 현 미국 표준 방식인 AES로 128~256비트 키를 적용할 수 있어 보안성이 뛰어나며 공개된 알고리즘이라 누구나 사용 가능합니다. 그 전에는 DES(Data Encrytion Standard)라는 알고리즘이 1975년부터 사용되고 있었으나 너무 오래되어 취약점이 발견됨에 따라 이를 대체 하기 위해 등장한 것이 AES라고 합니다.

대칭형 암호는 훌륭한 암호화 방식이긴 하지만 키 배송 관련으로 결정적인 문제가 존재합니다. 어떻게든 송신 측에는 수신 측에 암호키를 전달해야만 하고, 이 키가 배송과정에서 털리게 되면 아무리 뛰어난 암호화 알고리즘을 사용했더라도 속절없이 평문이 드러나게 됩니다. 안전하게 평문을 전달하기 위해 만든 것이 암호문인데, 정작 키는 안전하게 전달할 방법이 없다는 것이 가장 큰 단점입니다. 따라서 이 키 배송에 대한 방법이 여러가지 연구되었지만 결국 발상의 전환으로 키 배송 문제를 해결하기 위해 나타난 방식이 바로 비대칭형 암호 입니다.

AES를 간단하게 실습해 볼 수 있는 사이트가 있습니다. > AES encryption 홈페이지 바로가기

위 이미지에서 첫 번째 입력칸은 원하는 정보를 입력하는 칸을 의미하고, 두번째 입력칸은 우리가 사용할 키 즉, 절대로 노출되면 안되는 키를 입력하는 칸이 존재합니다. 마지막 은 비트를 선택해주게 되는데 이는 어떤 수준으로 암호화 할 것인지는 선택해주는 것입니다. 비트수가 높을 수록 그 암호화는 더욱 안전해지지만 그만큼 컴퓨팅 파워를 더 많이 사용하게 된다는 특징이 존재합니다.

홈페이지의 하단에 Encrypt 버튼을 눌러주면 아래와 같은 암호문이 등장합니다.

이 암호문을 그냥 단순히 바라보면 절대 원래 정보를 알아낼 수 없죠. 그러나 이 안에는 제가 적었던 ‘비밀 비밀’이라는 정보가 들어가져 있습니다.
이를 풀기 위해서는 많은 컴퓨팅 파워와 시간이 들게 되고 그만큼 안전한 것이 특징입니다.

그러면 이제 만들어진 암호를 복사하고 다시 첫번째 입력칸에 적어봅니다.

이 암호문을 풀기위해 같은 키를 사용해야겠죠? 이제 Decrypt 버튼을 눌러봅니다.

원래 제가 적었던 정보가 나타나게 됩니다.

2. 비대칭형(공개키 암호) 알고리즘

암호화, 복호화에 서로 다른 키가 사용되는 방식으로 하나의 키는 공개키로 사용하는 것이 특징입니다. 키 배송의 문제를 근본적으로 차단하여 안정성이 높지만 대칭키 방식에 비해 속도가 느리다는 단점이 존재합니다. 대표적으로는 RSA가 있습니다.

  • RSA: 공개키 암호 시스템의 하나로 암호화뿐만 아니라 전자서명이 가능한 최초의 알고리즘으로 주로 적은양의 데이터나 전자서명에 사용합니다. 대칭키인 DES, AES보다 속도가 느리기 때문에 메시지 암호화에는 쓰이지 않고 주로 키를 암호화하는데에 사용됩니다.

비 대칭형 암호는 이름 그대로 암호화 키와 복호화 키가 다른 것 입니다. 암호화를 하면 하나의 키 쌍이 생기고 이 두개의 키는 수학적으로 밀접한 관계를 가지게 됩니다. 두개의 키를 각각 A키, B키 라고 했을 때 A키로 암호화 한 암호문은 B키로만 복호화 할 수 있고, B키로 암호화 한 암호문은 A키로만 복호화 할 수 있습니다. 따라서 이 중 하나의 키만 비밀로 하고(이를 비밀키, 개인키라고 합니다) 다른 하나의 키는 누구에게 공개해도(이를 공개키라고 합니다) 가능한 키가 됩니다.

이렇게 둘 중 하나의 키는 반드시 공개 되어야 통상적인 사용이 가능함으로 공개키 암호라고도 불립니다. 공개키로 암호화한 암호문은 어짜피 개인키를 가진 사람만이 풀어볼 수 있기 때문에 상호간에 공개키만 교환하고 상대의 공개키로 암호화를 해서 데이터를 교환하면 자신의 개인키로 복호화를 하게 됩니다. 따라서 키 배송 문제는 근본적으로 발생하지 않게 됩니다.

예로 들어 인터넷 뱅킹을 생각해 봅시다.

  1. 사용자가 인터넷 은행 사이트에 접속하게 되면 사용자 컴퓨터에는 공개키와 비밀키가 생성 됩니다.
  2. 사용자 컴퓨터에서 공개키가 은행으로 전송되면 은행에서는 중요한 정보를 공개키로 암호화하며 암호문을 사용자에게 전달합니다.
  3. 사용자는 비밀키로 암호문을 해독하여 중요한 정보를 은행과 공유하며 통신을 하게 됩니다.

이러한 공개키 방식은 공인인증서, 전자서명 등에서 사용하고 있는 것이 특징입니다.

하지만 비대칭형 암호는 암호화, 복호화가 대칭형 암호에 비해 현저하게 느리다는 문제점이 존재합니다. 따라서 현실적으로는 비대칭형 암호를 이용해 대칭형 암호의 키를 배송하고 실제 암호문은 대칭형 암호를 사용하는 식으로 상호 보완적으로 이용하는 것이 일반적입니다. 그리고 비대칭형 암호도 약점이 없는것은 아닙니다. 중간자 공격이 바로 그 예시 입니다.

중간자 공격: 해커가 중간에서 통신을 가로채어 수신자에게는 송신자인 척하고 송신자에게는 수신자인 척 해서 양쪽의 공개키와 실제 암호화에 사용되는 대칭키를 모두 얻어내는 기법을 의미합니다.

Xcode 자동완성(Auto complete)이 안되는 경우 해결 방법!

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Xcode 자동완성(Auto complete)이 안되는 경우

  1. CMD + SHIFT + K > 프로젝트 클린
  2. CMD + SHIFT + OPTION + K > 빌드폴더 클린 / Xcode 내 Product > Clean Build Folder로도 가능
  3. Preference > Locations 에서 DerivedData 회색 화살표 클릭 > 파인더에서 DericedData 지우기

Expected superview but found nil when attempting make constraint `equalToSuperview`. 에러 해결하기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Expected superview but found nil when attempting make constraint ‘equalToSuperview’

추가하려는 객체를 addSubView 했는지 살펴보자….

iOS Target에 대한 정의, 분리하는 방법, 분리하여 사용했을 때 마주했던 이슈(Target Membership)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Target

기본적으로 회사에서는 여러 타겟을 나눠서 사용을 하고 있을것이고 이렇게 만들어진 타겟은 빌드할 때 각자 빌드하는 타겟이 설정됩니다.

타겟에 대한 문서 설명 먼저 봐보자!

A target specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace. A target defines a single product; it organizes the inputs into the build system—the source files and instructions for processing those source files—required to build that product. Projects can contain one or more targets, each of which produces one product.

이 문서를 직역해보자면, 타겟은 Xcode 프로젝트를 빌드할 때 빌드 과정에서 어떤 리소스, 소스파일들을 포함할 지 설정할 수 있고 빌드 과정의 순서 등 프로젝트의 큰 설정을 지정할 수 있는 것을 의미하는 것 같다. 그리고 이때 중요한 점은 하나의 타겟이 하나의 프로덕트인 것으로 즉, 타겟에 따라 하나의 프로젝트에서 여러 버전으로 프로덕트를 분리할 수 있는 것이다. 그렇기 때문에 기본적으로 회사에서는 Qa, Dev, DevTest 이런 식으로 각각의 타겟이 분리되어 있을 것이다.

즉 간단하게 타겟의 특징을 설명하자면,

  1. 하나의 타겟은 하나의 프로덕트이다.
  2. 하나의 프로젝트는 여러개의 타겟(프로덕트)로 이루어질 수 있다.
  3. 타겟별로 빌드 설정을 다르게 할 수 있다.

Target 분리하기

타겟 분리하는 실습을 해보기 위해 TargetProject라는 새 프로젝트를 생성해보았다.

타겟을 분리하는 방법은 아래와 같다.

1. Target 우클릭 > Duplicate 선택

그러면 아래 이미지와 같이 복사본 타겟이 하나 생성됩니다.

2. 복사된 버전을 원하는 이름으로 변경

이후, 이 복사된 버전을 원하는 이름으로 설정해줍니다.
저는 아래와 같이 TargetProjectDev로 이름을 변경해 주었습니다.

3. 빌드 환경에서 복사된 target 있는지 확인

4. Info.plist 설정

새롭게 Target을 생성했을때 Info.plist 역시 복사가 되어 자동으로 생성됩니다.
그러나 이름은 아래와 같이 TargetProject copy-info.plist로 되어있죠. 원하는 이름으로 수정해봅니다.

그리고 수정한 이름을 Xcode 프로젝트에서도 인식을 해줘야 하기에 Target > Build settings > Packaging 에서 변경한 이름으로 똑같이 변경해줍니다.

이렇게 완료한다면 이제 각각 타겟에 대한 설정은 어느정도 완료되었습니다.
이후부터는 이제 각자의 프로젝트 개발 상황에 맞게 설정을 해주면 됩니다.

이렇게 나뉜 타겟이 제대로 설정되었는지 확인해 봅시다.

5. Target 이름으로 구분해 빌드시 간단한 로그 확인해보기

만들어준 타겟의 Build settings > Swift Compiler - Custom Flags 에서 전처리문에서 구분하고 싶은 이름으로 변경해줍니다.
주의할 점은 꼭 -D라는 문장을 전에 입력해 주어야 인식이 가능하다는 점입니다.

이제 프로젝트로 돌아가 확인하고 싶은 뷰컨트롤러에서 다음과 같은 코드를 입력해주세요.

#if DEV
print("I'm Dev Target")
#else
print("I'm not Dev Target")
#endif

그러면 상황에 따라 이렇게 로그가 찍히는 것을 볼 수 있을 것 입니다!


Target을 분리하여 사용했을 때 마주했던 이슈

타겟을 분리해서 사용하던 중 A라는 뷰 컨트롤러를 생성해 코드를 진행하였고 이 A 뷰 컨트롤러를 B 뷰 컨트롤러에서 불러오려고 하였습니다. 간단한 예시 코드입니다.

A viewController

final class PracticeViewController: UIViewController {
    static func instance() -> PracticeViewController? {
        return PracticeViewController()
    }
}

B viewController

func pushPraticeVC() {
  guard let viewController = PracticeViewController.instance() else { return }
  self.navigationController?.pushViewController(viewController, animated: true)
}

그런데 이때 이와같은 에러가 발생하였습니다.

Cannot find 'PracticeViewController' in scope

아무리 찾아봐도 이유를 찾을 수 없었습니다.
그러는 와중에 해당 아티클을 보았습니다.

Cannot find type in scope after re-generating Core Data models

문제는 이와 같습니다.

A 뷰컨트롤러의 Target Membership과 B 뷰컨트롤러의 Target Membership이 달랐습니다.
그러니 B 뷰컨트롤러에서는 A 뷰컨트롤러에 접근을 할 수 없었던 것이죠.

아티클의 답변에서도 나왔듯, 보통을 뷰 컨트롤러를 만들때 자동으로 target Membership이 설정되는데, 이 새로운 파일에서는 이와같은 동작이 되지 않은 것입니다.
이를 해결하기 위해서는 아래와 같이 Target Membership 에서 직접 설정해주면 간단하게 해결 됩니다.

따라서 타겟을 분리해 사용할 때에는 이렇게 Target Membership을 한번씩 확인해 주는 습관!! 꼭 가지자!

iOS 코드로 UITableView 구현해보기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


코드로 UITableView 구현

개인적으로 스토리보드를 애용해 코드로 UI를 짜본적은 없었는데, 회사에서는 UI를 모두 코드로 진행하고 있었다.
그중 좀 흥미로웠던 부분이 있어 정리해보려고 합니당.

코드로 테이블뷰를 만드는것에 있어서 저는 기본 뷰컨트롤러에 테이블뷰를 추가하고, 테이블 뷰 셀을 가져오는 형식으로 구현할 것입니다.

private let tableView: UITableView = {
   let tableView = UITableView(frame: .zero, style: .grouped)
   tableView.register(PracticeChatTableViewCell.self, forCellReuseIdentifier: PracticeChatTableViewCell.identifier)
   tableView.separatorStyle = .none
   tableView.rowHeight = UITableView.automaticDimension
   tableView.estimatedRowHeight = 150
   return tableView
}()

뷰 컨트롤러에 변수로 테이블뷰를 만드는 방식이다.
이렇게 변수로 만들어놓은 테이블뷰를 화면에 위치 시켜야겠죠? 저는 snapKit을 사용했습니다.

func initViews() {
    super.initViews()

    self.view.addSubview(self.tableView)
    self.tableView.make { (make) in
        make.edges.equalToSuperview()
    }

    self.tableView.delegate = self
    self.tableView.dataSource = self
}

이렇게 만든 initView를 viewDidLoad에서 호출해주는 것 입니다.
그리고 만들어놓은 테이블뷰 변수는 addSubview를 통해 superView와 같은 크기로 위치시켜줍니다.

그리고 웨에 적인 PracticeChatTableViewCell 파일을 만들어줍니다.

import UIKit

class PracticeChatTableViewCell: UITableViewCell {
    static let identifier = "PracticeChatCell"

    private let containerView: UIView = {
        let containerView = UIView()
        containerView.backgroundColor = .red
        return containerView
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        self.contentView.addSubview(self.containerView)
        self.containerView.make { (make) in
            make.edges.equalToSuperview()
        }
    }

    required init?(coder: NSCoder) {
        fatalError("")
    }
}

여기서 가장 흥미로웠던 점은 셀을 만들때 init을 생성해줘야한다는 부분이었다.

일반적으로 셀 파일에서는 앞서 뷰컨트롤러에서 했듯이 initView 함수를 만들어놓고 각 객체들의 설정들을 다뤄줬었는데(스토리보드 사용시) 코드로 이를 진행할 때에는 반드시 init을 생성해줘야한다. 그 이유는 인터페이스 빌더에서는 자동으로 이 객체들을 초기화 해주지만, 코드에서는 인터페이스 빌더를 사용하는 것이 아니기 때문에 직접 초기화를 해줘야 하는 것이다. 이렇게 초기화를 해주지 않으면 아무것도 뜨지 않는다.

실제로 아무생각없이 init을 안해줬더니 정말 아무것도 뜨지 않았다..!

뷰 컨트롤러의 전체 코드는 아래와 같다.

import UIKit

final class PracticeViewController: UIViewController {
    static func instance() -> PracticeViewController? {
        return PracticeViewController()
    }

    private let tableView: UITableView = {
        let tableView = UITableView(frame: .zero, style: .grouped)
        tableView.register(PracticeChatTableViewCell.self, forCellReuseIdentifier: PracticeChatTableViewCell.identifier)
        tableView.separatorStyle = .none
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 150
        return tableView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        initViews()
    }

    func initViews() {
        super.initViews()

        self.view.addSubview(self.tableView)
        self.tableView.make { (make) in
            make.edges.equalToSuperview()
        }

        self.tableView.delegate = self
        self.tableView.dataSource = self
    }
}

extension PracticeViewController: UITableViewDelegate, UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: PracticeChatTableViewCell.identifier, for: indexPath)
        return cell
    }
}