1. props로 하위 콤포넌트에 전달 

useState는 상태 값과 그 값을 갱신하는 함수를 반환합니다.

const [state, setState] = useState(initialState);
setState(newState);

다음은 간단한 useState를 사용하고 props를 통해서 하위 콤퍼넌트로 전달하여 화면에 노출하는 코드입니다.

import React, { useState } from 'react';

const Welcome = (props) => {
  return (
    <>
    <h1>Hello, {props.name}</h1>
    </>
  )
}

const App = () => {
  //상태값
  const [age, setAge] = useState(42);
  return (
    <div className="App">
      <header className="App-header">
        <Welcome name={age}/>
      </header>
    </div>
  )
}

useState를 이용해서 [age, setAge] state 변수 age와 state를 갱신할 수 있는 setAge함수가 만들어지고 

setAge로 값을 설정하면 값이 갱신이되고 컴포넌트를 리 렌더링되게 됩니다.

 

하위 콤포넌트에서 해당 상태값을 참조하기 위해서는 props를 통해서 인자로 넘겨야 해서

상태값이 많거나 하위 component 복잡도가 높아질수록 관리의 어려움이 있습니다.

useContext를 통해서 전달 하도록 개선합니다.

 

2. useContext를 이용하여 global state 

context는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법입니다. 그러한 데이터로는 현재 로그인한 유저, 테마, 선호하는 언어 등이 있습니다.

 

사용방법은 상위 콤포넌트에서 Context를 생성해서 Context.Provider 로 값을 하위로 전달을 하면

하위 컴포넌트에서 필요시 구독하여 값을 사용할 수 있습니다.

 

1. React.createConntext(initialState) 로 Context생성

2. 상위 콤포넌트에서 Context.Provider value로 전달 

3. 하위 콤포넌트에서 useContext를 이용해서 참조 

...
const initialState = {
  index: 0
}
const Reducer = (state, action) => {
  console.log("action : " + action);
  switch (action.type) {
    case 'plus': {
      return { index: state.index + action.value}
    }
    default: {
      return state
    }
  }
}
...


//상위 컴포넌트
export const Context = React.createContext(initialState);
function App() {
  const [state, dispatch] = useReducer(Reducer, initialState)

  return (
    //하위로 value 전달
    <Context.Provider value={{state, dispatch}}>
       <Main />
       <Body />
    </Context.Provider>
  );
}

//하위 콤포넌트 사용하기 
...
//useContext를 이용하여 값을 가져와 사용
const {state, dispatch} = useContext(Context);
...
return (
     {state.index}
)

 

3.전체 소스

프로젝트 생성후 실행 

npx create-react-app my-app
npm start

 

App.js

import React, {useReducer} from 'react';
import Main from "./components/Main";
import Body from "./components/Body";
import './App.css';


const initialState = {
  index: 0
}

const Reducer = (state, action) => {
  console.log("action : " + action);
  switch (action.type) {
    case 'plus': {
      return { index: state.index + action.value}
    }
    default: {
      return state
    }
  }
}

export const Context = React.createContext(initialState);
function App() {
  const [state, dispatch] = useReducer(Reducer, initialState)

  return (
    <Context.Provider value={{state, dispatch}}>
       <Main />
       <Body />
    </Context.Provider>
  );
}

export default App;
import React, {useReducer} from 'react';
import Main from "./components/Main";
import Body from "./components/Body";
import './App.css';


const initialState = {
  index: 0
}

const Reducer = (state, action) => {
  console.log("action : " + action);
  switch (action.type) {
    case 'plus': {
      return { index: state.index + action.value}
    }
    default: {
      return state
    }
  }
}

export const Context = React.createContext(initialState);
function App() {
  const [state, dispatch] = useReducer(Reducer, initialState)

  return (
    <Context.Provider value={{state, dispatch}}>
       <Main />
       <Body />
    </Context.Provider>
  );
}

export default App;

 

Main.js

import React, {useContext, useState, useReducer} from 'react';
import {Context}  from "../App.js";

const Main = () => {
    const {state, dispatch} = useContext(Context);
    return (
      <div className="Main">
      main
      <header className="App-header">
        <p>
        {state.index}
        </p>
        <li
          onClick={()=>{ dispatch({ type: 'plus', value: 1 }) }}
        >
          Plus
        </li>
      </header>
    </div>
    )
}

export default Main;

 

Body.js

import React, {useContext, useState, useReducer} from 'react';
import {Context}  from "../App.js";
const BodyField = (props) => {
  return (
    <>
    <h1>{props.name}</h1>
    </>
  )
}

const Body = () => {
  //값전달
  const {state, dispatch} = useContext(Context);
  return (
    <div className="Body">
      body
      <header className="App-header">
        <BodyField name={state.index}/>
      </header>
    </div>
  )
}

export default Body;

 

결과

App에서 Main과 Body콤포넌트를 가지고 있고 하위 콤포넌트에서 index값을 참조 plus를 클릭시 동시에 갱신이 되는 

간단한 소스를 확인해보았고 Context를 통해서 하위콤포넌트에서 상태값을 공유하는 방법을 사용해 보았습니다.

 

 

 

자세한 내용은 하기 URL에서 확인 할 수 있습니다.

ko.reactjs.org/docs/getting-started.htmlko.reactjs.org/docs/hooks-reference.html#usecontext

 

Hooks API Reference – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

1. 네트워크 드라이버 종류

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
f5d0db8f813a        bridge              bridge              local
13d25bc6a925        host                host                local
b1f2996399bf        none                null                local

위와 같이 docker 는 3개의 네트워크를 이용하게 됩니다.

  bridge : 기본 네트워크 드라이버입니다. 드라이버를 지정하지 않은 경우 default bridge를 사용하게됩니다.

  host :  호스트 네트워킹을 직접 사용합니다.

  none : 모든 네트워킹을 비활성화합니다.

 

2. bridge network를 사용하여 테스트해보기

 

하기 두개의 컨테이너를 실행시켜 봅니다

$ docker run -dit --name alpine1 alpine ash
$ docker run -dit --name alpine2 alpine ash

 

실행중인 컨테이너를 확인해 봅니다.

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
22654255a870        alpine              "ash"               6 seconds ago       Up 5 seconds                            alpine2
55159257b891        alpine              "ash"               15 seconds ago      Up 14 seconds                           alpine1

 

하기 명령으로 기본 네트워크를 지정하지 않았으므로  bridge 네트워크에 연결이 된 것을 확인할 수 있습니다.

$ docker network inspect bridge
...
"Name": "bridge",
...
"IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
 ...
 "Containers": {
            "22654255a8706b9d3ed0efe8b4c2bdf0d99d036910f1205aec5df262a080feb8": {
                "Name": "alpine2",
                "EndpointID": "122a834f82c6675487efe1277e4e074395812cacf57f9ae2fb233d5ed458e4ab",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            },
            "55159257b891c72e810e02ead5b7fedf755f9d5f22d24297de39edc69f54f5d9": {
                "Name": "alpine1",
                "EndpointID": "01d895b00c6f52fa184d130a4413335f083b788a5c10787c779be38edda46348",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },

 

서로 연동이 잘되기 위하여  alpine1을 연결해 봅니다.

$ docker attach alpine1

//alpine1 -> alpine2로 연결확인 (ok)
# ping -c 2 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.249 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.084 ms

//컨테이너 이름으로 연결확인 (ng)
ping -c 2 alpine2
ping: bad address 'alpine2'

 

 

 

3. 서로 컨네이너명으로 연결해 보기 (사용자 정의 브릿지 네트워크 사용)

docker network create --driver bridge alpine-net

 

하기 명령으로 잘 생성된 것을 확인할 수 있습니다.

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
eb7be8cc8508        alpine-net          bridge              local
f5d0db8f813a        bridge              bridge              local
13d25bc6a925        host                host                local
b1f2996399bf        none                null                local

 

테스트 콘테이너 실행

//두개는 네트워크에 연결하가고 alpinne3은 네트워크 지정없이 기본 네트워크에 연결 
$docker run -dit --name alpine1 --network alpine-net alpine ash
$docker run -dit --name alpine2 --network alpine-net alpine ash
$docker run -dit --name alpine3 alpine ash
$docker run -dit --name alpine4 alpine ash
$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
6058ce3e109e        alpine              "ash"               49 seconds ago      Up 48 seconds                           alpine4
3df2d9c59b55        alpine              "ash"               27 minutes ago      Up 27 minutes                           alpine3
33a086be0054        alpine              "ash"               27 minutes ago      Up 27 minutes                           alpine2
3d88d4a925df        alpine              "ash"               27 minutes ago      Up 27 minutes                           alpine1

 

네트워크 inspect 확인

$docker network inspect bridge
...
"Name": "bridge",
...
"Containers": {
            "3df2d9c59b559d64df58f79d336a5495d1ef01a500a07b8d0b083fd8c15682f9": {
                "Name": "alpine3",
                "EndpointID": "0927f81fa6a69146e3cd75b05932b4d628223f266383d920c513c96943d505a5",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            },
            "6058ce3e109e88e87161f2b780fc18c7f2df4c884ac27e2aafdd958e90794447": {
                "Name": "alpine4",
                "EndpointID": "47248479369fcaba4c2f3f1535e1a7f47b0c5a0847bdc450618ad300b980491e",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            }
        },

...


$docker network inspect alpine-net
...
"Name": "alpine-net",
...
"Containers": {
            "33a086be0054693a5b4500f01264104637a0d3e3c2d89747996d89376754d3fd": {
                "Name": "alpine2",
                "EndpointID": "104deba1e3b9d9391846d8d3ea1dea4b80b7a4fc025aaded62c0d48c97ff5ff5",
                "MacAddress": "02:42:ac:13:00:03",
                "IPv4Address": "172.19.0.3/16",
                "IPv6Address": ""
            },
            "3d88d4a925df434b45f474fd2ff2f81199f738640d013053d0a2a9bdc49d8fa7": {
                "Name": "alpine1",
                "EndpointID": "42d16b7058b6bd136f3d02d4c0c42d61cfe5534d252704dfcd882cf054d01614",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
            }
        },
...

 

연결 테스트

$ docker container attach alpine1

//alpine2으로 컨테이너 명으로 ping

/ # ping -c 2 alpine2 (ok)

PING alpine2 (172.19.0.3): 56 data bytes

64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.099 ms

64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.088 ms



//alpine3으로 ping

ping -c 2 alpine3 (ng)

ping: bad address 'alpine3'



ping -c 2 172.17.0.2 (ng)

packets transmitted, 0 packets received, 100% packet loss

4. 정리

위 테스트를 그림으로 정리를 다시 하면

 

위 테스트를 통해서

  • 같은 네트워크에 있는 컨테이너간만 통신 가능

  • 사용자가 만든 외부 네트워크를 이용하면 컨테이너 이름으로 연결가능

이러한 이해는 여러 서비스를 연결할때 내부 ip를 매번 확인하면서 할 수 없기 때문에

외부 네트워크를 생성해서 컨테이너 이름으로 연결하는 것을 확인하는 테스트였습니다.

 

 

자세한 내용은 하기 document에서 확인할 수 있습니다.

https://docs.docker.com/network/

 

'DevOps' 카테고리의 다른 글

[Docker] HTTPS Let 's Encrypt 인증서 발급받기  (0) 2020.12.14
[Docker] Dockerfile command  (0) 2020.11.20
[Docker] docker 주요 명령어  (0) 2020.11.20
[nginx] nginx 프록시 설정  (0) 2020.11.17

linux 서버에서 root로 ssh를 이용하는경우 무작위 대입공격 시도에 당할 수 있어 보안에 매우 취약하다고 볼 수 있습니다. 

 root계정으로 접속이 불가능하도록 설정을 해보도록 하겠습니다. 

 

1. useradd [새계정]

   - passwd로 계정에 패스워드 입력 

2. vim /etc/ssh/sshd_config

   - PermitRootLogin no로 설정 

3. service sshd restart로 ssh 데몬을 재시작 

4. 새 계정 ssh 접속 

 

 

 

 

 

이미지를 만들기 위하여 Dockerfile를 사용을 합니다. 

Dockerfile내의 주요 명령어를 정리해 보았습니다.

docs.docker.com/engine/reference/builder/

 

Dockerfile reference

 

docs.docker.com

 

Dockerfile 빌드 

build

Dockerfile로부터 이미지 생성
docker build .
docker build -f /path/to/a/Dockerfile .

 

Dockerfile내 명령어

FROM

베이스이미지를 불러옵니다.
FROM [--platform=<platform>] <image> [AS <name>]
ROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

RUN

RUN <command>
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
RUN ["/bin/bash", "-c", "echo hello"]

CMD

CMD ["executable","param1","param2"]

COPY

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

ADD

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

ENTRYPOINT

컨테이너가 시작되었을때 실행
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

VOLUME

마운트 포인트를 생성
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

WORKDIR

도커파일내의 RUN, CMD, ENTRYPOINT, COPY and ADD가 실행되는 위치를 지정
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

EXPOSE

컨테이너가 listens할 port를 지정
EXPOSE <port> [<port>/<protocol>...]
EXPOSE 80/udp

 

 

1. 도커란 

도커 컨테이너는 일종의 소프트웨어를 소프트웨어의 실행에 필요한 모든 것을 포함하는 완전한 파일 시스템 안에 감싼다. 여기에는 코드, 런타임, 시스템 도구, 시스템 라이브러리 등 서버에 설치되는 무엇이든 아우른다. 이는 실행 중인 환경에 관계 없이 언제나 동일하게 실행될 것을 보증한다.

ko.wikipedia.org/wiki/%EB%8F%84%EC%BB%A4_(%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4)

 

기본 명령어 

모든 자세한 내용은 하기 document문서에서 확인하면 상세정보를 확인할 수 있습니다.

https://docs.docker.com/reference/

 

로그인

docker login [OPTIONS] [SERVER]

 

 

이미지 관련

이미지 찾기

Docker 허브에서 이미지를 검색
docker search [OPTIONS] TERM
docker search busybox

이미지 다운로드

레파지토리에서 이미지를 pull
docker pull [OPTIONS] NAME[:TAG|@DIGEST]
docker pull debian
docker pull debian:jessie

이미지 보기

이미지 리스트를 보여준다.
docker images [OPTIONS] [REPOSITORY[:TAG]]
docker images java
docker images java:8

이미지 실행

이미지에서 새로운 컨테이너를 실행한다.
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
docker run --name test -it debian

이미지 만들기

Dockerfile로 부터 이미지를 생성
docker build [OPTIONS] PATH | URL | -
docker build http://server/context.tar.gz
docker build -t app .

이미지를 삭제

docker rmi [OPTIONS] IMAGE [IMAGE...]
docker rmi fd484f19954f
docker rmi test2:latest
docker rmi -f fd484f19954f
docker rmi -f $(docker images -a -q)

이미지 히스토리

docker history [OPTIONS] IMAGE
docker history docker

레파지토리에 이미지 올리기

docker push [OPTIONS] NAME[:TAG]
docker push registry-host:5000/myadmin/rhel-httpd

 

컨테이너 관련

컨테이너 정지

docker stop [OPTIONS] CONTAINER [CONTAINER...]
docker stop my_container

컨테이너 시작

docker start [OPTIONS] CONTAINER [CONTAINER...]
docker start my_container

컨테이너 종료

docker kill [OPTIONS] CONTAINER [CONTAINER...]
docker kill my_container

컨테이너 확인

컨테이너 리스트를 보여준다
docker ps [OPTIONS]
docker ps -a

컨테이너 삭제

docker rm [OPTIONS] CONTAINER [CONTAINER...]
docker rm /redis
docker rm --force redis
docker rm $(docker ps -a -q)

컨테이너 명령

docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
docker exec -it ubuntu_bash bash
docker exec -it ubuntu_bash pwd

볼륨 생성

docker volume create [OPTIONS] [VOLUME]
$ docker volume create hello
hello
$ docker run -d -v hello:/world busybox ls /world

이미지 정보 보기

docker inspect [OPTIONS] NAME|ID [NAME|ID...]
docker inspect --format='{{.Config.Image}}' $INSTANCE_ID

 

'DevOps' 카테고리의 다른 글

[Docker] HTTPS Let 's Encrypt 인증서 발급받기  (0) 2020.12.14
[Docker] 네트워크, 컨테이너간 통신  (0) 2020.12.12
[Docker] Dockerfile command  (0) 2020.11.20
[nginx] nginx 프록시 설정  (0) 2020.11.17

nginx 란 ?

www.nginx.com/resources/glossary/nginx/

NGINX 는 웹 서비스, 리버스 프록시, 캐싱,로드 밸런싱, 미디어 스트리밍 등을위한 오픈 소스 소프트웨어입니다. 
최고의 성능과 안정성을 위해 설계된 웹 서버로 시작되었습니다. 
HTTP 서버 기능 외에도 NGINX는 이메일 (IMAP, POP3 및 SMTP) 용 프록시 서버와 HTTP, TCP 및 
UDP 서버용 역방향 프록시 및로드 밸런서로도 작동 할 수 있습니다.

 

저는 nginx를 웹 프록시 서버로 접근시 톰켓 서버로 연동 되도록 설정해 보겠습니다.

자세한 내용은 하기 내용들을 참고하면 될 것 같습니다.

https://nginx.org/en/docs/beginners_guide.html

https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/#proxying-http-traffic-to-a-group-of-servers

 

1. nginx.conf 설정

nginx설치 후 nginx.conf 파일을 하기와 같이 8080으로 받으면 내부 9090포트로 전달하도록 설정하였습니다.

server {
        listen       8080;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;

        location / {
            proxy_set_header    Host $http_host;
            proxy_set_header    X-Real-IP $remote_addr;
            proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header    X-Forwarded-Proto $scheme;
            proxy_set_header    X-NginX-Proxy true;

            proxy_pass http://127.0.0.1:9090;
            proxy_redirect      off;
            charset utf-8;
         }
...

2. nginx 재 시작 

위 설정 후 nginx restart 8080으로 접근하면 정상적으로 동작하는 것을 확인하였습니다.

1. 배포시에 war를 톰켓에 올리기 위하여 war를 export하기 위해서  

하기 가이드를 참고하여 적용해 보도록 하겠습니다

https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-traditional-deployment

 

“How-to” Guides

Spring Boot has no mandatory logging dependency, except for the Commons Logging API, which is typically provided by Spring Framework’s spring-jcl module. To use Logback, you need to include it and spring-jcl on the classpath. The recommended way to do th

docs.spring.io

 

1. SpringBootApplication에 SpringBootServletInitializer 상속 

@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer{ 

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
	
	@Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 
		return builder.sources(DemoApplication.class); 
	}
}

 

2. build.gradle 설정 

1. id 'war' 추가 

plugins {
	id 'org.springframework.boot' version '2.3.2.RELEASE'
	id 'io.spring.dependency-management' version '1.0.9.RELEASE'
	id 'java'
	id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
	id 'war'
}

 

2. bootwar 적용

docs.spring.io/spring-boot/docs/current/gradle-plugin/api/org/springframework/boot/gradle/tasks/bundling/BootWar.html

bootWar {
	archiveBaseName = 'demoproject'
 	archiveVersion="0.0.1-SNAPSHOT"
}

3. dependencies 추가 

dependencies {
	...
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' 
    ...
}

 

3. 확인

1. gradle bootWar실행 

2. war 확인 libs폴더 하위에 생성

위와 같이 war를 제대로 추출되는것을 확인 하였습니다. 

 

1. AES란

AES는 미국 정부가 채택한 이후 전 세계적으로 널리 사용되고 있다.

1977년 공표된 DES를 대체한 AES는, 암호화와 복호화 과정에서 동일한 키를 사용하는 대칭 키 알고리즘이다.

 

2.키 길이

AES 암호화 키의 길이는

128 bit - 16byte 

192 bit - 24byte 

256 bit - 32byte 

의 세 가지 중 하나가 될 수 있으며,

각각 AES-128, AES-192, AES-256으로 불린다. 

암호화 키의 길이에 따라 실행하는 라운드의 수가 다른데, 각각 10, 12, 14 라운드를 실행한다.

 

3. 운영방식

EBC, CBC차이

전자 코드북(electronic codebook, ECB)은 운용 방식 중 가장 간단한 구조를 가지며, 암호화하려는 메시지를 여러 블록으로 나누어 각각 암호화하는 방식으로 되어 있다.

 

전자 코드북은 모든 블록이 같은 암호화 키를 사용하기 때문에 보안에 취약하다.

 

암호 블록 체인 (cipher-block chaining, CBC) 방식은 1976년 IBM에 의해 개발되었다.[4] 각 블록은 암호화되기 전에 이전 블록의 암호화 결과와 XOR되며, 첫 블록의 경우에는 초기화 벡터가 사용된다. 초기화 벡터가 같은 경우 출력 결과가 항상 같기 때문에, 매 암호화마다 다른 초기화 벡터를 사용해야 한다.

 

 

CBC 방식은 현재 널리 사용되는 운용 방식 중 하나이다

 

4. AES256 CBC방식 소스 구현해보기

https://developer.android.com/reference/javax/crypto/Cipher

위 Cipher클래스에서 Algorithm Modes Paddings를 하기와 같이 설정하여 확인해 보도록 하겠습니다.

AES/CBC/PKCS5Padding으로 확인해보겠습니다.

PKCS5Padding은

AES는 블록사이즈가 16바이트 크기로 16바이트의 배수가 아니라면

마지막 블록의 빈 부분을 채워주는 패딩옵션으로 PKCS5Padding로 적용해보겠습니다.

 

* 참고할만한 java 소스 코드 확인시

AES 블록은 16byte이고 AES키 사이즈는 16, 24, 32이며 

라운드 횟수는 키사이즈에 의해서 라운드 횟수가 정해지고

IV사이즈는 블록사이즈와 동일하게 가져야 하는 것을 확인할 수 있습니다.

 

AESConstants.java

interface AESConstants {
    // AES block size in bytes.
    int AES_BLOCK_SIZE = 16;
    // Valid AES key sizes in bytes.
    // NOTE: The values need to be listed in an *increasing* order
    // since DHKeyAgreement depends on this fact.
    int[] AES_KEYSIZES = { 16, 24, 32 };
}

 

AESCrypt.java

키사이즈에 의해서 라운드 횟수가 정해지는것을 확인할 수 있습니다.

/**
  * Return The number of rounds for a given Rijndael keysize.
  *
  * @param keySize  The size of the user key material in bytes.
  *                 MUST be one of (16, 24, 32).
  * @return         The number of rounds.
  */
private static int getRounds(int keySize) {
    return (keySize >> 2) + 6;
}

 

CipherCore.java

블록사이즈와 initial vector사이즈가 동일한 것을 확인할 수 있습니다.

if (params instanceof IvParameterSpec) {
    ivBytes = ((IvParameterSpec)params).getIV();
    if ((ivBytes == null) || (ivBytes.length != blockSize)) {
        throw new InvalidAlgorithmParameterException
            ("Wrong IV length: must be " + blockSize +
              " bytes long");
    }
}

 

Cipher클래스를 이용해서 하기와 같이 구성을 해보았습니다.

//키길이 AES 256
private val KEY_SIZE = 32
private val SECRET_KEY = "0".repeat(KEY_SIZE)
//모드 CBC
private val AES_TRANSFORMATION = "AES/CBC/PKCS5PADDING"
//AES initial Vector == 블록사이즈 16고정
private val IV = "1234567890123456"
private val ivParameterSpec = IvParameterSpec(IV.toByteArray())
private fun secretKeySpec(): SecretKeySpec {
    return SecretKeySpec(SECRET_KEY.toByteArray(), "AES")
}

private fun encrypt(plainText: String): ByteArray  {
    val cipher = Cipher.getInstance(AES_TRANSFORMATION)
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec(), ivParameterSpec)
    return cipher.doFinal(plainText.toByteArray())
}

private fun decrypt(cipherText: ByteArray?): String {
    val cipher = Cipher.getInstance(AES_TRANSFORMATION)
    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec(), ivParameterSpec)
    return String(cipher.doFinal(cipherText))
}

private fun encode(byteArray: ByteArray): ByteArray {
    val decodedBytes = Base64.encode(byteArray, Base64.DEFAULT)
    return decodedBytes
}

private fun decode(encodedString: String): ByteArray {
    val decodedBytes = Base64.decode(encodedString, Base64.DEFAULT)
    return decodedBytes
}

 

실행

val plainText = "|" + REQUEST_DATE
Timber.tag(TAG).i("plainText: " + plainText)
val encryptText = encode(encrypt(plainText))
Timber.tag(TAG).i("encrypt : " + encryptText)

var resultDecrypt = decrypt( decode(resultStr))
Timber.tag(TAG).i("result decrypt : " + resultDecrypt)

 

결과

plainText: |1604809558
encrypt : [B@162f532
decrypt : |1604809558

 

SO파일에 문자열을 숨기고 암호화키로 사용할 경우 감출 수 있는지 테스트 확인하기 위하여

기본 프로젝트를 이용하여 간단히 만들어 보도록 하겠습니다.

 

참고 URL

https://developer.android.com/studio/projects/add-native-code?hl=ko

 

프로젝트 생성

1. new project -> Native c++ 디폴트 프로젝트 생성

 

 

2. 빌드후 실행

 

 

3. 정상적으로 호출되는 것을 확인했고 소스코드를 확인해 보겠습니다.

생성한 프로젝트의 cpp>native-lib.cpp stringFromJNI메소드명을 사용할 Java_패키지_액티비티_stringFromJNI으로 이름 메소드 변경필요

extern "C" JNIEXPORT jstring JNICALL 
Java_패키지_액티비티_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

액티비티에서 사용 소스

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // Example of a call to a native method
    findViewById<TextView>(R.id.sample_text).text = stringFromJNI()
}
external fun stringFromJNI(): String
companion object {
    init {
        System.loadLibrary("native-lib")
    }
}

 

so파일 export

1. Build > Build Bundles(s) / APK(s) > Build APK(s)를 선택합니다.

2. Build > Analyze APK를 선택합니다.

3. apk압축 해제후 lib하위 폴더에 so파일이 생성된 것을 확인할 수 있습니다.

 

 

 

1. 다른 방법으로

https://developer.android.com/ndk/guides/cmake?hl=ko

toolbar gradle 명령에서 externalNativeBuild를 실행하면

 

 

로그에 so 파일 생성되고 경로가 정보창으로

...

app/build/intermediates/cmake/release/obj/x86_64/libnative-lib.so

...

으로 생성된 것을 확인할 수 있습니다.

 

다른 프로젝트에서 사용하기

so 폴더 파일들을 사용할 프로젝트에 jniLibs폴더 생성 모두 복사

Timber.tag(TAG).i("jni : " + stringFromJNI())
external fun stringFromJNI(): String

companion object {
    // Used to load the 'native-lib' library on application startup.
    init {
        System.loadLibrary("native-lib")
    }
}

 

로그로 잘 찍히는 것을 확인하였습니다.

 

 

 

SO 파일 HEX 값 확인

이와 같이 테스트한 목적은 암호화키를 하드 코딩을 숨길 수 있을까하는 관점에서 테스트해보았으며

hex editor에 문자열이 노출되는 것을 확인되어 파악이 쉽진않겠지만 보안이 보장되지 않는것을 확인하였습니다.

 

 

이상입니다.

프로미스에 대해서 사용해보겠습니다. 

자세한 설명은 하기 URL을 참고 하시기 바랍니다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

프로미스는 비동기 작업에 대해서 완료 또는 실패를 결과를 콜백을 전달하는 대신, 콜백을 첨부하는 방식의 객체입니다.

 

Promise

기본동작 예제  

  1. asyn를 원하는 작업에 대해서 Promise로 감싸고 파라미터로 전달된 resolve or reject로 성공 실패를 호출합니다.

  2. 결과를 return을 받으면 then, catch구문을 이용하여 결과, 에러에 대해서 처리를 할 수 있습니다.  

Promise를 여러게 처리하기 위해서  

하기와 같이 Promise.All을 이용하여 처리를 할 수 있습니다.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});


Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

 

Promise 흐름은 다음과 같습니다.

 

async / await 비동기처리

또다른 방법으로 async / await를 사용할 수 있습니다.

async function 선언은 AsyncFunction객체를 반환하는 하나의 비동기 함수를 정의합니다

 

기본 동작 

기존에 만들어 놓은 asyncFunction을 호출 

async function asyncCall() {
    console.log('calling’);
    try {
        const result = await asyncFunction();
        console.log("asyncCall : " + result);
    } catch(err) {
        //error 
        console.log(“error : " + err);
    }
    // expected output: "resolved"
}

내부적으로 async 함수는 항상 promise를 반환합니다. 만약 async 함수의 반환값이 명시적으로 promise가 아니라면 암묵적으로 promise로 감싸집니다.

 

async function foo() {
    return 1
}

위 코드는 아래와 같습니다.
function foo() {
    return Promise.resolve(1)
}
async function foo() {
    await 1
}

위 코드는 아래와 같습니다.
function foo() {
    return Promise.resolve(1).then(() => undefined)
}

위와 같이 비동기 처리에 대해서 사용해 보았습니다. 

+ Recent posts