손글씨 잘 써서 좋겠다

저자
공병각 지음
출판사
양문 | 2013-06-20 출간
카테고리
예술/대중문화
책소개
손끝으로 감성을 그리는 캘리그래퍼 되는 길! 디자이너이자 크리에...
가격비교



타이포그래피 교과서(개정판)

저자
제임스 크레이그, 아이린 코롤 스칼라, 윌리엄 베빙튼 지음
출판사
안그라픽스 | 2010-08-18 출간
카테고리
예술/대중문화
책소개
차근차근 배워 가는 타이포그래피 필수 안내서[타이포그래피 교과서...
가격비교



만년필입니다

저자
박종진 지음
출판사
엘빅미디어 | 2013-11-30 출간
카테고리
시/에세이
책소개
이 책은 번역서조차 한 권 없는 대한민국에서 최초로 소개하는 만...
가격비교



좋은 사진을 만드는 김주원의 DSLR 사진 강의

저자
김주원 지음
출판사
한빛미디어 | 2012-09-14 출간
카테고리
컴퓨터/IT
책소개
오로지 당신만을 위한 1:1 사진 과외!사진가 김주원이 10년 ...
가격비교



포토샵 사진 강의

저자
김주원 지음
출판사
한빛미디어 | 2012-05-30 출간
카테고리
컴퓨터/IT
책소개
세상은 왜!! 이런 포토샵 책을 만들어내지 못했을까? 단순히 따...
가격비교



사진구도

저자
정승익 지음
출판사
한빛미디어 | 2013-11-30 출간
카테고리
예술/대중문화
책소개
좋은 사진은 좋은 구도에서 나온다 사진의 핵심은 스킬이 아니라 ...
가격비교



인포그래픽이란 무엇인가

저자
마크 스미시클라스 지음
출판사
에이콘출판 | 2013-02-22 출간
카테고리
컴퓨터/IT
책소개
요약 인포그래픽은 인포메이션 그래픽의 준말로서 정보디자인, ...
가격비교




편집디자인

저자
커뮤니케이션 디오 지음
출판사
길벗 | 2011-12-15 출간
카테고리
예술/대중문화
책소개
편집디자인을 하기 위해 지켜야 할 절대법칙 10편집디자인을 하기...
가격비교




1%의 네트워크 원리(2ND EDITION)

저자
TSUTOMU TONE 지음
출판사
성안당 | 2009-01-12 출간
카테고리
컴퓨터/IT
책소개
이 책만큼 네트워크의 구조와 작동 원리에 대해 체계적으로 설명한...
가격비교


내용을 대충 훑어 봤을 때 웹에 관련된 네트워크 원리 책인 듯 하다.

얼마전 프로젝트를 하면서 무전기 앱을 만들었는데, TCP/IP 소켓 프로그래밍 경험밖에 없어서 그렇게만 구현을 했다.

기억하기론 음성 스트림의 경우 데이터의 일부를 잃더라도 재생을 함에 있어 큰 문제가 되지 않기 때문에

TCP가 아닌  UDP로 한다고 알고 있는데, 궁금한게 많아서 살펴본 책이다.

 헌데 웹에 관한 내용이다보니 TCP/IP 위주로 되어있어 조금 아쉽긴 했다. 웹 프로그래밍을 하면서 참고하면 좋을 듯.




소프트웨어 아키텍처 설계 가이드

저자
강승준, EVA스터디그룹 지음
출판사
프리렉 | 2013-12-09 출간
카테고리
컴퓨터/IT
책소개
평생을 개발자로 살 수 있도록 하는 자그만 씨앗이 되었으면 합니...
가격비교


난 아키텍처에 대해 관심만 있고 지식이 전무하다. 그래서 바로 눈이 바로 갔는지도 모르겠다.

그러고 보니 저자가 EVA구나. 출간일도 최근이구나. 얼른 사야겠다.

개발 환경

- IDE : Spring Tool Suite 3.3.0.RELEASE

- Spring Framework 3.1.1 (Web MVC 포함)

- OS : Max OSX 10.8.5


연습으로 해본 이미지 업로드 프로젝트입니다. 잘못된 내용이 있거나 개선할 내용이 있으면 댓글 남겨주시면 배우도록 하겠습니다 :)



먼저 STS에서 Spring MVC Web 프로젝트를 하나 만듭니다.








프로젝트 탐색기에 보이는 프로젝트의 모습입니다. 위 단계까지 따라오셨으면 아래에 보이는 파일 중에 없는 것 몇가지가 있습니다. 이 글을 끝까지 따라가면 보이는 프로젝트 파일의 모습입니다.

예제 프로젝트에서는 패키지를 따로 나누지 않고 통패키지(..)에 모든 클래스 파일을 담았습니다.






먼저 servlet-context.xml 파일에 Multipart 사용을 위해서 멀티파트 리졸버를 추가합니다.


<!-- Multipart를 받기 위한 리졸버 추가 -->
<beans:bean id="multipartResolver"
	class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>




MultipartResolver를 사용하게 되면 두개의 라이브러리가 추가적으로 필요합니다. Commons File Upload와 IO입니다.

메이븐을 사용한다면 의존 라이브러리를 추가해주고, 그 외 빌드 툴이나, 혹은 사용하지 않는다면 환경에 맞게, 두가지의 라이브러리를 추가해줍니다.

<!-- Apache Commons File Upload -->
<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.2</version>
</dependency>

<!-- Apache Commons IO 추가 -->
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-io</artifactId>
	<version>1.3.2</version>
</dependency><






이미지 업로드 컨트롤러 클래스 내용입니다.

이미지 업로드를 위한 폼 페이지와 결과 페이지 및 이미지를 얻기 위한 URL을 매핑합니다.

package com.nnoco.example;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class ImageUploadController {
	/*
	 * ImageView는 파일 시스템에 있는 이미지 파일을 응답으로 돌려주는 역할을 합니다.
	 * 뒷 부분에서 ImageView 클래스를 작성하게 됩니다.
	 */
	@Resource(name="imageView") ImageView imageView;

	/**
	 * 이미지를 관리하는 서비스 계층 클래스입니다. 예제에서는 디비를 사용하긴 버거워지므로
	 * 서비스 클래스를 따라하는 모양만 서비스인 클래스입니다.
	 */
	@Autowired ImageService imageService;
	
	/**
	 * 이미지 업로드를 위한 페이지 매핑 
	 */
	@RequestMapping("/uploadPage")
	private String uploadView() {
		return "upload";
	}
	
	/**
	 * 이미지 업로드 페이지의 폼에서 전송 시 받게 되는 메서드 
	 */
	@RequestMapping(value="/upload", method=RequestMethod.POST)
	private String upload(@RequestParam MultipartFile imageFile, ModelMap modelMap) {
		ImageFile fileInfo = imageService.save(imageFile);
		
		modelMap.put("imageFile", fileInfo);
		
		return "uploadComplete";
	}
	
	@RequestMapping("/image/{imageId}")
	private ImageView getImage(@PathVariable String imageId, ModelMap modelMap) {
		ImageFile imageFile = imageService.get(imageId);
		
		modelMap.put("imageFile", imageFile);
		
		return imageView;
	}
}




업로드한 이미지 정보를 담고있는 ImageFile 클래스입니다.

package com.nnoco.example;

public class ImageFile {
	/**
	 * 업로드한 이미지 파일이 저장될 경로
	 */
	public static final String IMAGE_DIR = "/web/upload_images/";

	private String id;
	private String contentType;
	private int contentLength;
	private String fileName;

	public ImageFile(String id, String contentType, int contentLength,
			String fileName) {
		this.id = id;
		this.contentType = contentType;
		this.contentLength = contentLength;
		this.fileName = fileName;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getContentType() {
		return contentType;
	}

	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

	public int getContentLength() {
		return contentLength;
	}

	public void setContentLength(int contentLength) {
		this.contentLength = contentLength;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

}






이미지 뷰 클래스입니다.

package com.nnoco.example;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;


@Component("imageView")
public class ImageView extends AbstractView{
	@Override
	protected void renderMergedOutputModel(Map model,
			HttpServletRequest req, HttpServletResponse res) throws Exception {
		ImageFile imageFile = (ImageFile)model.get("imageFile");
		
		// 응답 메시지에 파일의 길이를 넘겨줍니다.
		res.setContentLength(imageFile.getContentLength());

		// 응답의 타입이 이미지임을 알려줍니다.
		res.setContentType(imageFile.getContentType());
		
		// 파일로부터 byte를 읽어옵니다.
		byte[] bytes = readFile(imageFile.getFileName());
		write(res, bytes);
	}

	/**
	 * 파일로부터 byte 배열 읽어오기 
	 */
	private byte[] readFile(String fileName) throws IOException {
		String path = ImageFile.IMAGE_DIR + fileName;
		
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
		int length = bis.available();
		byte[] bytes = new byte[length];
		bis.read(bytes);
		bis.close();
		
		return bytes;
	}

	/**
	 * 응답 OutputStream에 파일 내용 쓰기
	 */
	private void write(HttpServletResponse res, byte[] bytes) throws IOException {
		OutputStream output = res.getOutputStream();
		output.write(bytes);
		output.flush();
	}
}




이미지 서비스 클래스입니다.

모양만 서비스이고, 구현은 전혀 아닙니다. 맵 자료구조를 사용해서 DB를 대신했습니다.

package com.nnoco.example;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class ImageService {
	private Map imageFilesMap;
	
	public ImageService() {
		init();
	}
	
	/**
	 * 초기화
	 */
	private void init() {
		imageFilesMap = new HashMap();
	}
	
	/**
	 * ID로 이미지 파일 가져오기
	 */
	public ImageFile get(String id) {
		return imageFilesMap.get(id);
	}
	
	/**
	 * Multipart File을 파일로 저장하고 DB(를 빙자한 맵)에 업로드 파일 정보 저장, 실패하는 경우 null리
	 */
	public ImageFile save(MultipartFile multipartFile) {
		// UUID로 유일할 것 같은 값 생성.. 낮은 확률로 중복 가능성이 있음
		String genId = UUID.randomUUID().toString();
		ImageFile imageFile = null;
		
		try {
			String savedFileName = saveToFile(multipartFile, genId);
			
			imageFile = new ImageFile(genId, 
					multipartFile.getContentType(),
					(int)multipartFile.getSize(),
					savedFileName);
			
			imageFilesMap.put(genId, imageFile);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return imageFile;
	}
	
	/**
	 * Multipart File의 내용을 파일로 저장, 저장 후 저장된 파일 이름을 반환
	 */
	private String saveToFile(MultipartFile src, String id) throws IOException {
		String fileName = src.getOriginalFilename();
		byte[] bytes = src.getBytes();
		String saveFileName = id + "." + getExtension(fileName);
		String savePath = ImageFile.IMAGE_DIR + saveFileName;

		/* 파일 쓰기 */
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(savePath));
		bos.write(bytes);
		bos.flush();
		bos.close();
		
		return saveFileName;
	}
	
	/**
	 * 파일이름으로부터 확장자를 반환하는 메서드
	 * 파일이름에 확장자 구분을 위한 . 문자가 없거나. 가장 끝에 있는 경우는 빈문자열 ""을 리턴
	 */
	private String getExtension(String fileName) {
		int dotPosition = fileName.lastIndexOf('.');
		
		if (-1 != dotPosition && fileName.length() - 1 > dotPosition) {
			return fileName.substring(dotPosition + 1);
		} else {
			return "";
		}
	}
}


파일 업로드를 위한 JSP 페이지와 결과 확인 페이지입니다.

upload.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>이미지 업로드</title>
</head>
<body>
	<form action="./upload" method="post" enctype="multipart/form-data">
		<input type="file" name="imageFile"><br>
		<input type="submit" value="전송">
	</form>
</body>
</html>


uploadComplete.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>업로드 결과 페이지</title>
<style type="text/css">
	.failed {
		color: red;
		font-style: bold;
		font-size:18pt;
	}
</style>
</head>
<body>
	<c:choose>
		<c:when test="${imageFile != null }">
		파일 업로드 완료
		<ul>
			<li>파일 ID : ${imageFile.id }</li>
			<li>저장된 파일 이름 : ${imageFile.fileName }</li>
			<li>파일 길이 : ${imageFile.contentLength }</li>
			<li>MIME 타입 : ${imageFile.contentType }</li>
		</ul>
		
		<img src="${pageContext.request.contextPath}/image/${imageFile.id}">
		</c:when>
		<c:otherwise>
		<span class="failed">파일 업로드 실패</span>		
		</c:otherwise>
	</c:choose>
</body>
</html>




여기까지 필요한 설정과 파일을 추가하고, 서버에 올려 접속을 하면 아래와 같은 아주 간단한 폼과 함께 파일을 업로드 해볼 수 있습니다.

이미지 업로드라고 하긴 했지만 사실 이미지 업로드를 위해 한 일은 없고, 파일 업로드를 하는 작업만 한 것 같네요.





위 예제 프로젝트는 아래 파일을 다운로드하시어 살펴보실 수 있습니다.

ImageUploadExample.zip


1. 멀티모니터에 자동으로 WPF Window 배치

2. 키넥트 2대 Color Frame 동시 사용


1.

화면 해상도 DPI에 따른 실제 디스플레이 Resolution과, GUI 프로그램에서 사용하는 Resultion이 달라 DPI에 따라 바꿔줘야함,


즉 현재 개발 환경은 맥북 프로에서 키넥트 개발을 위해서 Boot Camp로 Windows 7을 사용중인데,

Retina Display의 해상도는 2880 * 1920인데, 제어판의 디스플레이에 들어가보면 크게 보기가 설정된 것을 알 수가 있다.



이렇게 된 경우 Gui에서 pixel을 계산할 때 원래 해상도 설정에서 (만약 위처럼 크케(L)로 설정 되어있다면) 150%를 줄여서 계산해야 한다.


Win Form 프로그래밍에서는 System.Windows.Forms 가 참조에 추가되어있지 않아서 Screen 클래스를 사용하기 위해서 직접 추가해줘야 한다.


System.Windows.Forms.Screen.AllScreens.FirstOrDefault()

System.Windows.Forms.Screen.AllScreens[index]

System.Windows.Forms.Screen.PrimaryScreen

여기서 얻을 수 있는 Screen을 통해 얻는 디스플레이의 사이즈와


System.Windows.SystemParameters.PrimaryScreenHeight or Width

처럼 WPF에서 제공하는 API로 스크린의 크기를 가져오면 값이 다르다..


만약 이 값이 다르지 않았다면 각 모니터에 창을 배치하는 것을 어떻게 해야할 지 떠올리지 못했을 수도 있겠다.


왜 인지 모르겠으나 WPF 프레임워크에서는 다중 디스플레이 환경 정보를 가져올 수 있는 인터페이스를 제공하지 않는 듯하다.



2.

키넥트 두대를 하나의 PC에 꽂아 동시에 양쪽 키넥트의 ColorFrame을 보여주려고 했는데, 안되서 몇 시간을 삽질했는데, SDK 레벨에서 연산이 제대로 되지 않는지 안된다. 혹여나 같은 프로세스에서 두개의 키넥트에 접근한 것이 문제가 되는가 싶어서 다른 프로세스에서 각각 다른 키넥트에 접근했는데 여전히 똑같다.

그래서 내린 결론은 안된다 였다. 원래 FPS가 30인 것이 0으로 떨어져버린다.


키워드를 Two Kinect at one 처럼 해서 검색해보면 사례가 적긴 하지만 이전 SDK 버전이나 C++ SDK에서는 되긴 하는 것 같다.



'머리가 뛰다' 카테고리의 다른 글

흠..  (0) 2014.03.01
책갈피 대신 책갈피 줄  (0) 2013.03.06
Behavior-Driven-Development BDD?  (0) 2013.02.22
Redmine 설치기  (0) 2013.02.16
Phonegap create 오류  (0) 2012.11.25

11월 20일 소마 3기 인증식 참가 때문에 수업을 빼고 갈 수 밖에 없었는데, 이날 수업은 실용한자, 실용영어회화, 그리고 과사 근로가 있었다.

실용한자는 참 타이밍 좋게도 이번 수업은 쉬자고 하셨고, 실용영어회화 수업은 교수님께 다른 분반의 다른 날 수업에 들어가겠다고 말씀드렸다.

그리고 그렇게 대체 수업을 하기로 한게 어제였고, 분반이 달라 파트너 없이 수업을 듣다보니 자연스레 교수님과 파트너가 되어 수업을 진행했다.

그리고 오늘 다시 실용영어회화 수업. 강의실에 앉아 있는데 교수님이 오시더니

블라블라블라 same.. 블라블라블라.. do you want to stay here? 라길래 혹시 내가 가면 핸디캡이 있냐고 물어봤더니, 씨익 웃으면서 출석부에 체크를 하시고는 노 핸디캡이라신다.

다시 있을거냐며 묻길래 나도 씨익 웃으면서 강의실에서 나왔다.

바쁜 와중에 이번 주는 이래저래 일이 잘 풀리는 주다.

'마음이 뛰다' 카테고리의 다른 글

OpenShift Node 설정 중...  (0) 2014.09.18
새로운 영역의 공부  (0) 2014.07.25
뚜렷도?  (0) 2013.10.10
Noblesse Oblige  (0) 2013.08.29
나를 완성해 가는 길  (0) 2013.08.23

 

사진 출처 - 영문 위키피디아(Canon EOS 70D)


DSLR이 너무 갖고 싶어서 하사 첫 월급을 받자마자 일시불로 EOS 450D를 샀었더랬다.

책을 읽거나, 누군가에게 배웠던 것은 아니지만 이래 저래 듣는 것들로 배워가며,

잘 찍은 사진의 구도를 따라서 찍어보기도 하고, 광각 렌즈도 아닌 번들렌즈로 광각을 찍어보겠다고,

싼 가격의 광각 필터를 사서 끼워 찍어보기도 했다.


1년여를 사용하다가 다시 학교로 복학하게 되어서는 생활고에 시달리다(...) 내 첫 DSLR과 이별을 고했다.


잘 찍지 못해도, 잘 알지 못해도 여전히 내게 카메라는 위시 리스트 1호다.

건국대에 미팅이 있어 서울에 올라갔던 날 지하철에서 EOS 70D 광고를 하는 걸 보고는 나는 역시 마음이 다시 동할 수 밖에 없다. EOS 450D 다음의 카메라는 요놈이 되지 않을까 싶다.


여유만 된다면 망원 렌즈도 하나 갖고프다. 인물 사진을 찍을 때면 항상 아쉬웠던게 렌즈 앞에만 서면 어색해지는 사람들의 표정이다. 물론 나도 포함된다.

찍은 사진을 살펴보노라면 잘나온 사진이라고 느낌이 드는 것은 아무래도 자연스러움이 묻어나는 사진일텐데,

대부분 그런 사진은 사진 속의 인물이 렌즈를 의식하지 않을 때인것 같다.

망원렌즈라면 피사체가 의식하지 않는 거리에서 자연스러운 모습을 좀 더 쉽게 담아 낼 수 있지 않을까?


어쨌거나.. 카메라는 뭐 좀 하려면 죄다 돈이여..



'Wish List' 카테고리의 다른 글

SSD Docking Station  (0) 2013.03.24
사고 싶다 기계식 키보드!!  (0) 2013.03.11
람보르기니 아벤타도르 J  (0) 2012.06.14
[책] 프로그래머가 알아야 할 97가지  (0) 2012.06.14

어떤 URL로 리다이렉트하거나, 링크를 클릭했을 때 이동하지 않고 다운로드 하게 하려면,

브라우저는 응답 헤더의 내용을 보고 URL로 이동할지 다운로드를 할지 결정하기 때문에

서버측에서 아래와 같은 헤더 정보를 담아 주어야 한다.


Content-Type: application/octet-stream

Content-Disposition: attachment; filename=<브라우저에서 저장할 파일 이름>




폰트의 비밀

저자
고바야시 아키라 지음
출판사
예경 | 2013-08-20 출간
카테고리
예술/대중문화
책소개
“폰트의 종류는 많은데, 과연 어떤 폰트를 쓰는 게 좋을까? ...
가격비교



좋아 보이는 것들의 비밀 캘리그래피

저자
왕은실 캘리그라피, 오문석 지음
출판사
길벗 | 2013-07-10 출간
카테고리
예술/대중문화
책소개
디지털 시대, 아날로그 감성으로 만나는 마음을 움직이는 문자 디...
가격비교



통찰

저자
최재천 지음
출판사
이음 | 2013-09-25 출간
카테고리
인문
책소개
★구 PC뷰어 및 전용 단말에서는 이용이 불가능 합니다★ ‘통섭...
가격비교


뚜렷도(뚜렷度) [발음 : 뚜렫또]

[명사] 영상이 헝클어지거나 흐리지 않고 분명한 정도.


2013. 10. 8 소프트웨어 프로젝트II-종합 설계 과목 중

이모군이 포토샵 작업을 하는 중에 아이콘의 경계에 계단현상이 나타난 것을 보고 "아 이거 어떻게 하지?"라고 하자

박모양이 "그거 있잖아요, 뭐라고 하지?? 그.. 뚜렷도 있잖아요 뚜렷도 그거 조정하면 안되요?"

에서 유래함.


원래 의도한 단어는 포토샵에서 블러링의 반대인 Distort를 표현하고자 한듯함.

'마음이 뛰다' 카테고리의 다른 글

새로운 영역의 공부  (0) 2014.07.25
Do you want to stay here?  (0) 2013.11.22
Noblesse Oblige  (0) 2013.08.29
나를 완성해 가는 길  (0) 2013.08.23
가입도 안된 사이트에서..  (0) 2013.03.12

지금 개발하고 있는 Touch me는 이벤트 광고 플랫폼으로 앱 사용자가 특정 광고에 참여 또는 공유하는 경우 일정 포인트가 쌓이게 된다. 참여 또는 공유 정보는 서버로 전송되고 유효한 액션인 경우에 포인트가 쌓이도록 해야하는데,

포인트는 현금과 1:1의 가치를 갖기 때문에 보안이 중요하다.

헌데 지금까지 보안을 고려한 개발을 제대로 해본적이 없어서, 어떻게 보호를 해야하고, 무엇을 보호해야할 지에 대해 모르는 상태다. 이 때문에 기본적인 암호화에 대한 공부를 하게 되었다.

지금까지는 인터넷에서 찾은 정보로 알게된 것이 전부이고, 더 공부를 하고 싶어서 '알아야 막는다 - 자바 JSP 해킹과 보안'과 '실용 암호학 : 보안 실무자를 위한 정보보호와 암호화 구현' 두 권의 책을 구매한 상태이다.

4학년 수업 중에 교재로 사용하는 정보 보호와 보안 이라는 책이 있었던 것 같은데 정확한 책 제목과 저자를 알지 못해 함께 구매하진 못했다.


앱을 통해 포인트를 적립하라는 HTTP 메시지의 암호화를 위해서는 무엇을 암호화 해야할까?

굳이 포인트 적립에 대한 메시지를 암호화하는 까닭은 현금과 포인트는 같은 가치를 가지기 때문에 유효하지 않은 포인트 적립, 즉 포인트 적립을 비정상적인 방법으로 적립할 수 있기 때문에 포인트 적립 메시지의 암호화를 총해 유효함을 검증하기 위함이다. 또한 비정상적인 광고 참여, 공유는 광고 효과와 서비스의 신뢰성과도 직결되기 때문에 서비스에서 중요한 부분일 수 있다.

사용자에게 광고 효과를 얻기 위해서 참여와 공유를 하도록 하고 이에 대한 보상으로 포인트를 지급하는 것인데, 광고 효과가 없는 포인트 지급은 의미 없는 지출일 뿐이니 말이다.


포인트 적립에 필요한 정보는 관련 광고, 유저 정보, 포인트 정보이다.

포인트 적립이 '유효함'을 보장하기 위해서는 사용자가 직접 어떤 액션을 해야한다는 것인데,

쓰다보니 굳이 암호화 하지 않더라도 서버에서 유효함을 보장하기 위한 1회성 토큰을 전송하는 것도 괜찮을 듯하다..


이 부분에 대해서는 아직 정리되지 않은 부분이 많아, 프로토콜에 대한 내용을 다시금 정리해봐야겠다.


서론이 너무 길었던 것 같다. 어쨌든.. 위의 고민을 하면서 알아봤던 RSA 암호화 알고리즘에 대해서 포스팅 해야겠다.

다시 잡설이지만 무엇인가를 배운 후에, 정리하거나 누군가에게 설명하는 일은 내가 다시 공부하는 것이라고 생각한다.


RSA 비대칭키 암호화 알고리즘

RSA 알고리즘은 공개키 암호 알고리즘의 하나로 이를 연구하였던 Ron Rivest, Adi Shamir, Leonard Adleman의 이름 첫글자를 따서 지어진 이름이다.

한국 위키피디아의 RSA 암호 문서의 내용에 따르면 RSA 암호 체계의 안정성은 큰 숫자를 소인수분해하는 것이 어렵다는 것에 기반을 두고 있다고 한다. 허나 1993년 피터 쇼어가 쇼어 알고리즘을 발표하면서 양자 컴퓨터를 이용하여 임의의 정수를 다항 시간 안에 소인수분해하는 방법을 발표하였다. 즉 양자 컴퓨터가 활성화된다면 RSA 알고리즘은 쉽게 깨지게 된다. 암호로써의 역할을 잃게 되는 것이다.

RSA 비대칭키 암호화 알고리즘은 위키피디아 문서에 잘 정리되어 있다.



비대칭키 암호화 알고리즘

암호화 알고리즘에는 대칭키 알고리즘과 비대칭키 알고리즘이 있다. 먼저 대칭키 알고리즘은 암호화/복호화에 사용되는 키가 동일한 알고리즘을 의미하고, 비대칭키 암호화 알고리즘은 암호화/복호화에 사용되는 키가 서로 다른 것을 의미한다. 여기서 암호화에 사용되는 키를 공개키(Public Key)라고 하고, 복호화에 사용되는 키를 비밀키(Private Key)라고 한다. RSA 알고리즘은 공개키, 비밀키 두개의 키를 생성하지만 암호화/복호화에 있어서 어느 하나만을 사용해야한다는 제약은 없다. 즉 암호화 시에 공개키를 사용하고 비밀키를 통해서 복호화를 할 수 있지만,  반대로 암호화 시에 비밀키를 사용하고 공개키를 통해서 복호화할 수도 있다.


일반적으로 대칭키 알고리즘이 비대칭키 알고리즘에 비해서 성능이 좋다. 따라서 대량의 데이터를 암호화 하기 위해서는 비대칭키 알고리즘의 성능은 떨어지게 된다. 성능상의 보정을 위해서 데이터는 대칭키 알고리즘으로 암호화 하고, 대칭키의 키만 비대칭키 알고리즘으로 암호화하는 방법을 사용하기도 한다.



자바에서 RSA 알고리즘의 사용

자바에서는 java.crypto 패키지 내에 암호화와 관련한 구현이 되어있다. Touch Me 에서 RSA 알고리즘 사용을 위해 간략하게 유틸리티 클래스를 작성해봤다.

주요 사용 클래스는 java.crypto 패키지의 Cipher, java.security 패키지의 KeyFactory, KeyPair, KeyPairGenerator, PrivateKey, PublicKey, RSAPrivateKeySpec, RSAPublicKeySpec이다.

package com.touchme.web.security;

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.apache.commons.codec.binary.Base64;

/**
 * RSA 비대칭키 암호화 알고리즘 사용을 위한 유틸리티 클래스
 * @author 이준영
 *
 */
public class RSAUtils {
	public static final String RSA = "RSA";
	public static KeyPair generateKeyPair() {
		KeyPair keyPair = null;
		try {
			keyPair = KeyPairGenerator.getInstance(RSA).generateKeyPair();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		
		return keyPair;
	}
	
	/**
	 * Public Key로 암호화한 후 결과로 출력된 byte 배열을 Base64로 인코딩하여 String으로 변환하여 리턴함
	 * @param text 암호화할 텍스트
	 * @param publicKey RSA 공개키
	 * @return Base64로 인코딩된 암호화 문자열
	 */
	public static String encrypt(String text, PublicKey publicKey) {
		byte[] bytes = text.getBytes();
		String encryptedText = null;
		try {
			Cipher cipher = Cipher.getInstance(RSA);
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			encryptedText = new String(Base64.encodeBase64(cipher.doFinal(bytes)));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		
		return encryptedText;
	}
	
	/**
	 * Base64로 인코딩된 문자열을 받아 decode 시킨 후 RSA 비밀키(Private Key)를 이용하여 암호화된 텍스트를 원문으로 복호화
	 * @param encryptedBase64Text Base64로 인코딩된 암호화 문자열
	 * @param privateKey RSA 비밀 키
	 * @return 복호화된 텍스트
	 */
	public static String decrypt(String encryptedBase64Text, PrivateKey privateKey) {
		byte[] bytes = Base64.decodeBase64(encryptedBase64Text.getBytes());
		String decryptedText = null;
		try {
			Cipher cipher = Cipher.getInstance(RSA);
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			decryptedText = new String(cipher.doFinal(bytes));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		
		return decryptedText;
	}
	
	/**
	 * RSA 공개키로부터 RSAPublicKeySpec 객체를 생성함
	 * @param publicKey 공개키
	 * @return RSAPublicKeySpec
	 */
	public static RSAPublicKeySpec getRSAPublicKeySpec(PublicKey publicKey) {
		RSAPublicKeySpec spec = null;
		try {
			spec = KeyFactory.getInstance(RSA).getKeySpec(publicKey, RSAPublicKeySpec.class);
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		
		return spec;
	}
	
	/**
	 * RSA 비밀키로부터 RSAPrivateKeySpec 객체를 생성함
	 * @param privateKey 비밀키
	 * @return RSAPrivateKeySpec
	 */
	public static RSAPrivateKeySpec getRSAPrivateKeySpec(PrivateKey privateKey) {
		RSAPrivateKeySpec spec = null;
		
		try {
			spec = KeyFactory.getInstance(RSA).getKeySpec(privateKey, RSAPrivateKeySpec.class);
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		
		return spec;
	}
	
	/**
	 * Moduls, Exponent 값을 이용하여 PublicKey 객체를 생성함
	 * @param modulus RSA Public Key Modulus
	 * @param exponent RSA Public Key exponent
	 * @return PublicKey 객체
	 */
	public static PublicKey getPublicKey(String modulus, String exponent) {
		BigInteger modulus_ = new BigInteger(modulus);
		BigInteger exponent_ = new BigInteger(exponent);
		PublicKey publicKey = null;
		
		try {
			publicKey = KeyFactory
					.getInstance(RSA)
					.generatePublic(new RSAPublicKeySpec(modulus_, exponent_));
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		
		return publicKey;
	}
	
	/**
	 * Modulus, Exponent 값을 이용하여 PrivateKey 객체를 생성함
	 * @param modulus RSA private key Modulus
	 * @param privateExponent RSA private key exponent
	 * @return PrivateKey 객체
	 */
	public static PrivateKey getPrivateKey(String modulus, String privateExponent) {
		BigInteger modulus_ = new BigInteger(modulus);
		BigInteger privateExponent_ = new BigInteger(privateExponent);
		PrivateKey privateKey = null;
		
		try {
			privateKey = KeyFactory
					.getInstance(RSA)
					.generatePrivate(new RSAPrivateKeySpec(modulus_, privateExponent_));
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		
		return privateKey;
	}
}


Base64

Touch Me에서는 문자열로 서버와 안드로이드 애플리케이션 간에 데이터를 주고받게 된다.

만약 JSON 포맷의 데이터라면 UTF-8로 인코딩된 문자열을 주고 받으므로 사람의 눈으로 문자를 확인하면 정상적인 JSON 문자열을 확인할 수 있다. 이 JSON 문자열을 암호화 하게 된다면 문자 인코딩이 적용되지 않은 바이트배열로 구성되어 있으므로 이를 문자열로 변환하여 사람의 눈으로 확인한다면 의미없는 문자 또는 폰트로 표현되지 않는 문자로 보여질 수 있다. 이 때 정상적인 문자로 보여주기 위해서 Base64로 인코딩한다.

간단하게 말하자면 Base64는 64진수를 의미한다. 6bit의 이진 데이터를 ASCII 문자로 매핑하여 변환하는데 숫자 0~1, a~z, A~Z, +, / 로 표현하게 된다. 이진 데이터가 6bit로 딱 떨어지지 않는 경우가 발생할 수 있다. 이를 표현하기 위해서 패딩(Padding) 문자 = 를 사용한다.

이진데이터의 길이 n (1bit를 1로 했을 때) 을 3으로 나눈 나머지 즉 r = n % 3 에서

r이 0인 경우 패딩문자를 붙이지 않는다.

r이 1인 경우 패딩문자 = 하나를 붙인다.

r이 2인 경우 패딩문자 = 두개를 붙인다.

Base64 인코딩 순서는 아래와 같다.

1. ASCII 테이블 매핑

2. 2진수 변환

3. 6bit 단위로 분할

4. 10진수 변환

5. Base64 테이블 매핑

6. 패딩 연산

문자 인코딩 방법은 Base64외에도 ASCII, URL, HTML 등이 있으며, allstar927님의 블로그에 '인코딩이란? (ASCII, URL, HTML, Base64, MS Script 인코딩)'이란 제목으로 깔끔하게 정리되어 포스팅이 되어있다.

Base64의 자바구현은 다양한 벤더에서 제공하고 있지만 Touch Me 에서는 Apache Commons Codec을 사용하였다.


내일이면 주문한 도서가 도착할 듯하다. 터치미 서비스의 신뢰성과 안전성을 위해서 체계적으로 정리해서 적용해봐야겠다.




* 참고 글

1. allstar927님 - 인코딩이란? (ASCI, URL, HTML, Base64, MS Script 인코딩)

2. www.java2s.com - Generate a RSA public key with given modulus and public/private exponent

3. www.javamex.com - RSA ecryption in Java

4. 剛宇님 - 자바 암호화 - RSA

5. 류지현(瀏智賢)님 - 자료구조 8. 대칭형 암호화, 비대칭형 암호화

6. 권남님 - RSA 기반 웹페이지 암호화 로그인

7. 오늘도 커피 3잔? - AES를 이용한 128비트 공통키의 생성과 암호화

8. 위키피디아 - RSA 암호 (영문, 한글)

+ Recent posts