본문 바로가기

보안/시큐어코딩

[시큐어코딩실습] 암호화 실습

NIST알고리즘 안전성 유지 기간및 최소 키길이 권고


암호알고리즘별 성능 비교: http://www.javamex.com/tutorials/cryptography/ciphers.shtml


화면: 

join.jsp

member_modify.jsp



[실습1]  해시함수, AES 암호화/복호화, byte[]->Hex/Hex->byte[] 배열로 변환하는 기능을 구현하는 OpenCrypt.java 파일을 생성한다.

 


  

(1) SHA-256 암호 알고리즘을 이용한 해쉬값을 반환하는 메서드를 구현한다.

 

   

getSHA256.txt    <-- 해쉬 함수를 이용하여 패스워드 암호화 작업을 수행한다.


 public static byte[] getSHA256(String source, String salt) {

           byte byteData[]=null;

           try{

               MessageDigest md = MessageDigest.getInstance("SHA-256"); 

               md.update(source.getBytes()); 

               md.update(salt.getBytes()); 

               byteData= md.digest();  

               System.out.println("원문: "+source+ "   SHA-256: "+

                                     byteData.length+","+byteArrayToHex(byteData));

           }catch(NoSuchAlgorithmException e){

               e.printStackTrace(); 

           }

           return byteData;

}

 
   

AES.txt     <-- AES 블록 암호화를 이용하여 회원명 암호화 작업을 수행한다.

 



(2) 대칭키 암호화에 사용되는 키를 생성한다. 키의 길이는 128비트를 사용한다.

 

 public static byte[] generateKey(String algorithm)

                  throws NoSuchAlgorithmException {

   

        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);  

        keyGenerator.init(128);

        SecretKey key = keyGenerator.generateKey();

        return key.getEncoded();  

 }     


  

(3) AES 암호 알고리즘을 이용한 암호화 메서드를 구현한다.


public static String encrypt(String msg, byte[] key) throws Exception {

     SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

     Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

     String iv = "AAAAAAAAAAAAAAAA";

     cipher.init(Cipher.ENCRYPT_MODE, 

                     skeySpec,

                     new IvParameterSpec(iv.getBytes()));        

     byte[] encrypted = cipher.doFinal(msg.getBytes());     

     return  byteArrayToHex(encrypted);

  }

AES 암호 알고리즘을 사용하는 경우 Cipher 객체 생성시 암호화 모드를 설정하게 되는데 CBC 모드를 사용하는 경우 암호화 작업 수업시 초기벡터값(iv) 값을 키의 길이 만큼 지정하여야 한다. 

  
 

(4) AES 암호 알고리즘을 이용한 복호화 메서드를 구현한다. 


public static String decrypt(String msg,byte[] key ) throws Exception {

      SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

      String iv = "AAAAAAAAAAAAAAAA";

      cipher.init(Cipher.DECRYPT_MODE, 

                     skeySpec,

                     new IvParameterSpec(iv.getBytes()));  

      byte[] encrypted = hexToByteArray(msg);

      byte[] original = cipher.doFinal(encrypted);  

      return new String(original); 

 }



(5) HEX String --> Byte Array 변환


public static byte[] hexToByteArray(String hex) {

      if (hex == null || hex.length() == 0) {

          return null;

      }

   

      byte[] ba = new byte[hex.length() / 2];

      for (int i = 0; i < ba.length; i++) {

          ba[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);

      }

      return ba;

  }


 
(6) Byte Array --> Hex String 변환


 public static String byteArrayToHex(byte[] ba) {

      if (ba == null || ba.length == 0) {

          return null;

      }

   

      StringBuffer sb = new StringBuffer(ba.length * 2);

      String hexNumber;

      for (int x = 0; x < ba.length; x++) {

          hexNumber = "0" + Integer.toHexString(0xff & ba[x]);

   

          sb.append(hexNumber.substring(hexNumber.length() - 2));

      }

      return sb.toString();

  } 

 

 

RSA를 이용한 암복호화


rsa.txt


 // RSA 공개키/개인키를 생성한다.

 public static List<Key> gernerateKeyPair() throws Exception {

ArrayList<Key> keyList = new ArrayList<Key>();

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");

keyPairGenerator.initialize(2048);

KeyPair keyPair = keyPairGenerator.genKeyPair();

Key publicKey = keyPair.getPublic();

Key privateKey = keyPair.getPrivate();

keyList.add(publicKey);

keyList.add(privateKey);

return keyList;

 }


 // RSA 공개키를 이용한 암호화

public static String encryptRSA(String msg, Key key) throws Exception {

Cipher cipher = Cipher.getInstance("RSA");

cipher.init(Cipher.ENCRYPT_MODE,key);

byte[] enc=cipher.doFinal(msg.getBytes());

return byteArrayToHex(enc);  

}


 // RSA 개인키를 이용한 복호화

 

 public static String decryptRSA(String msg, Key key) throws Exception {

Cipher cipher = Cipher.getInstance("RSA");

cipher.init(Cipher.DECRYPT_MODE,key);

byte[] dec=cipher.doFinal(hexToByteArray(msg));

return new String(dec);

}

 

 

TestController에서의 암복화테스트 


rsa_test.txt


@RequestMapping(value="/test/encrypt_test.do", method = RequestMethod.POST)

@ResponseBody

public String testEncryption(HttpServletRequest request, HttpSession session)

                     throws Exception{

StringBuffer buffer=new StringBuffer();

String data=request.getParameter("data");

String type=request.getParameter("type");

// 키 저장소에서 키를 불러온다. (별도의 안전한 DB에 키가 저장되어 있어야 한다)

byte[] key=(byte[])session.getAttribute("key");

List<Key> pairKey=(List<Key>)session.getAttribute("pairKey");


// 키가 생성되어 있지 않은 경우 키를 생성하여 저장한다.

if ( key == null ) {

key=OpenCrypt.generateKey("AES");

session.setAttribute("key", key);

}

if ( pairKey == null ){

pairKey=OpenCrypt.gernerateKeyPair();

session.setAttribute("pairKey", pairKey);

}

switch ( TestUtil.getInt(type)  ) {

case 0:

buffer.append(OpenCrypt.getSHA256(data,"1234")); break;

case 1:

buffer.append(OpenCrypt.encrypt(data, key)); break;

case 2:

buffer.append(OpenCrypt.decrypt(data, key)); break;

case 3:

buffer.append(OpenCrypt.encryptRSA(data, pairKey.get(0))); break;

case 4:

buffer.append(OpenCrypt.decryptRSA(data, pairKey.get(1))); break;

   default:

    buffer.append("잘못된 요청입니다.");

}

        return buffer.toString();

}

 

 

 

[실습2] OpenEG 게시판 사이트의 회원가입시 입력되는 패스워드와 사용자이름은 암호화 하여 저장한다. 패스워드는 SHA-256 해시 함수를 salt를 사용하여 암호화 하여 저장하며, 사용자이름은 AES-128 알고리즘을 이용하여 암호화 하여 저장한다.  

 

* 회원가입/회원정보 수정을 처리하는 컴포넌트들 

    MemberService.java     - 회원가입/정보수정 서비스 요청을 처리하는 서비스 컴포넌트

    MemberDao.java         - 회원정보 DAO 인터페이스

    MemberDaoImpl.java   - 회원정보 DAO 인터페이스 구현 클래스

    member.xml                - 회원정보테이블 CRUD 처리 SqlMap

    SecVO.java                  - 암호화키,해시함수 솔트 처리를 위한 Bean

   

 


(1)  회원정보를  안전하게 관리하기 위한 board_security 테이블 정보를 사용하기 위한 모델 컴포넌트를  생성한다. 


SecVO.java


Security 테이블의 내용을 처리하기 위한 Bean

public class SecVO {

    private String userId;

    private String salt;

    private String secKey;

    

    public SecVO() {}

    public SecVO(String userId, String salt, String secKey) {

       super();

       this.userId = userId;

       this.salt = salt;

       this.secKey = secKey;

     }

 

     public String getUserId() {

         return userId;

     }

 

     public void setUserId(String userId) {

         this.userId = userId;

     }

 

     public String getSalt() {

          return salt;

     }

 

     public void setSalt(String salt) {

          this.salt = salt;

     }

 

     public String getSecKey() {

           return secKey;

     }

 

     public void setSecKey(String secKey) {

           this.secKey = secKey;

     }




(2)  추가된 보안 정책(salt, 암호화키)를 검색하기위한 쿼리문을 추가한다. 


member.xml      <-- 기존 member.xml 파일에 아래 두개의 쿼리문을 추가한다.

<select id="selectSecurity" parameterClass="String"  resultClass="SecVO">

    select salt,seckey

    from JMBoard_Security where userId=#userId#

 </select>

 

 <insert id="insertSecurity" parameterClass="SecVO"  >

    insert into JMBoard_Security(userId,salt,secKey)

    values(#userId#, #salt#,#secKey#)

 </insert>


 

(3) 보안 정책을 적용하기 위한 DB 처리 작업을 인터페이스에 추가한다. 


MemberDao.java

 void insertSecurity(SecVO salt);

 SecVO selectSecurity(String userId);



(4)  추가된 DAO 를 구현한다. 


MemberDaoImpl.txt     <-- Security 정보를 입력하거나, 검색하는 DAO 작업을 추가한다. 


@Override

 public void insertSecurity(SecVO sec) {

          getSqlMapClientTemplate().insert("member.insertSecurity", sec);

 }

 @Override

 public SecVO selectSecurity(String userId) {

           return (SecVO) getSqlMapClientTemplate()

                        .queryForObject("member.selectSecurity", userId);

 }


 


(5)  회원가입 처리 모듈을 수정한다. 


새로운 회원을 추가할 때, salt값과 암호화 키를 생성하여 security 테이블에 저장한다.

생성된 salt 값을 이용하여 패스워드를 해시한다.

생성된 암호화키를 이용하여 AES 128 블록암호화 정책을 사용하여 사용자 이름을 암호화하여 저장한다.


MemberService.txt    <-- 회원등록시 패스워드 암호화에 사용될 SALT를 생성하고, 블록 암호화에 사용될 암호화 Key를 생성하여 Security 테이블에 저장한다.

 

public int addMember(MemberModel memberModel) {

  if ( dao.selectMember(memberModel.getUserId()) != null ) {

   return 1;

  } else {

        try {

          byte[] key=OpenCrypt.generateKey("AES");  

          System.out.println("생성된 키길이: "+key.length);

          SecVO sec=new SecVO(memberModel.getUserId(),

                                             UUID.randomUUID().toString(),

                                             OpenCrypt.byteArrayToHex(key));

          dao.insertSecurity(sec);

          memberModel.setUserName(OpenCrypt.aesEncrypt(

                      memberModel.getUserName(), 

                      OpenCrypt.hexToByteArray(sec.getSecKey())));

          memberModel.setUserPw(

                    new String(OpenCrypt.getSHA256(

                                       memberModel.getUserPw(),sec.getSalt())));

          dao.insertMember(memberModel);

          return 3;

       }catch(Exception e ){

           System.out.println("회원 가입 처리 에러");

           return 2;

       }

  }

 }



(6)  회원정보 수정 모듈을 수정한다. 


public boolean modifyMember(MemberModel memberModel){

   try {

      SecVO sec=dao.selectSecurity(memberModel.getUserId());

      memberModel.setUserPw(

            new String(OpenCrypt.getSHA256(memberModel.getUserPw(),sec.getSalt())));

      memberModel.setUserName(OpenCrypt.aesEncrypt(

                                                 memberModel.getUserName(), 

                                                OpenCrypt.hexToByteArray(sec.getSecKey())));

      dao.updateMember(memberModel);

   }catch(Exception e){

      return false;

   }

   return true;      

}



(7)  회원정보 조회 모듈을 수정한다. 


회원정보를 조회하여 암호화되어 있는 회원명을 복호화하여 회원정보에 설정한다.


public MemberModel findMember(String userId ) {

  MemberModel m=dao.selectMember(userId);

  SecVO sec=dao.selectSecurity(userId);

  try {

      m.setUserName(OpenCrypt.aesEncrypt(m.getUserName(), 

                             OpenCrypt.hexToByteArray(sec.getSecKey())));

  } catch (Exception e) {

     System.out.println("회원정보조회 에러");

  }

  return m;

 }



 

(8)  로그인 처리 모듈을 수정한다. 

  

암호화된 저장된 값을 복호화하여 모델객체에 저장한다.

LoginService.txt


public LoginSessionModel checkUserId(String userId, String userPw) {

   SecVO sec=mdao.selectSecurity(userId);

   userPw=new String(OpenCrypt.getSHA256(userPw,sec.getSalt()));

   LoginSessionModel  m= dao.selectUserId(userId, userPw);

   try {

      m.setUserName(OpenCrypt.aesDecrypt(m.getUserName(), 

                              OpenCrypt.hexToByteArray(sec.getSecKey())));

  } catch (Exception e) {

    System.out.println("로그인 정보 조회 에러");

  }

  return m;

}