티스토리 뷰

윈도우에서 GUID 같은 모바일 장치에서도 고유값을 구하는 방법이 필요하다. 무엇으로 할까? 스마트 폰에서는 전화번호, 현재 위치 등도 고유한 값이긴하지만, IMEI 와 IMSI 값이 일반적인 고유치가 될 수 있을 것이다. 일단, IMEI 장비 고유값에 대해서 알아보자.

용어설명
MEID(Mobile Equipment IDentifier) - IMEI의 헥사값(14자), CDMA의 ESN(Electronic Serial Number, 8자)를 대신함
IMSI(International Mobile Subscriber Identity) - GSM/UMTS 에서 모바일 유저의 고유값(15자)
IMEI(International Mobile Equipment Identity) - GSM/WCDMA 및 위성폰에서의 고유값(코드14자+체크용1자=총15자)

우리 나라는 WCDMA 방식을 쓰니까 모바일 장치의 고유값은 IMEI일 것이다.
단, USIM에 대한 고유값 곧 사용자에 대한 고유값은 IMSI이고, 지금 구하는 값은 오직 모바일 장치 고유값에 대한 부분이다.
 
1. 아이폰[각주:1]
애플은 배포본(혹은 외부개발자용), 내부 개발자용으로 헤더 파일을 두개 쓰는 것 같다. 애플 더럽다 퉤퉤.[각주:2]
Message/NetworkController.h 이 헤더 파일에 있는 클래스로 심카드에 관한 정보를 가지고 올 수 있는데, 외부개발자용 헤더에는 IMEI 정보가 없다.

일단, 다음과 같은 헤더 파일을 RealNetworkController.h 를 만들어서
/* 
*     Generated by class-dump 3.1.1. * 
*     class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard. 
*/
#import "NSObject.h"
@class NSString, NSTimer;
@interface NetworkController : NSObject
{    struct __SCDynamicStore *_store;
    NSString *_domainName;
    unsigned int _waitingForDialToFinish:1;
    unsigned int _checkedNetwork:1;
    unsigned int _isNetworkUp:1;
    unsigned int _isFatPipe:1;
    unsigned int _edgeRequested:1;
    NSTimer *_notificationTimer;
}
+ (id)sharedInstance;
- (void)dealloc;
- (id)init;
- (BOOL)isNetworkUp;
- (BOOL)isFatPipe;
- (BOOL)inAirplaneMode;
- (id)domainName;
- (BOOL)isHostReachable:(id)fp8;
- (id)primaryEthernetAddressAsString;
- (id)IMEI;
- (id)edgeInterfaceName;
- (BOOL)isEdgeUp;
- (void)bringUpEdge;
- (void)keepEdgeUp;
- (void *)createPacketContextAssertionWithIdentifier:(id)fp8;
@end
위 헤더 파일(RealNetworkController.h)를, 개별 프레임워크에 넣고(아래 그림 참조[각주:3])

IMEI 정보가 필요한 부분에 다음 코드를 넣자.
NetworkController *ntc = [NetworkController sharedInstance];
NSString *imeistring = [ntc IMEI];
imeistring  값이 USIM의  imei 값이다.
단, iOS 4에서 이게 돌아가는지는 모른다. 아이폰이 없다. -.-;

2. 안드로이드폰[각주:4]
com.android.internal.telephony.Phone  phone;
String imeistring;
if ( phone.getPhoneName().equals("CDMA"))
{
  imeistring = phone.getMeid();
}
else
{
  imeistring  = phone.getDeviceId();
}
imeistring  값이 USIM의  imei 값이다. 물론 이 방법이 편리하다. 하지만, 배포중인 SDK에서 com.android.internal.telephony.Phone 는 없다.[각주:5] -.-; 다음의 방법을 써야 한다.

import android.telephony.TelephonyManager;
import android.content.Context;
...
String imeistring, imsistring;

{
 TelephonyManager telephonyManager;
             
 telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
 imeistring = telephonyManager.getDeviceId();
 imsistring = telephonyManager.getSubscriberId();
}
에뮬레이터는 imei 에서는 000000000000000 imsi 는 310260000000000 을 돌려준다.
윈도우 모바일이나, 심비안과 같이 퍼미션이 필요한데, READ_PHONE_STATE 퍼미션이 필요하단다.
자세한 건 http://developer.android.com/reference/android/telephony/TelephonyManager.html 를 참조하자.

AndroidManifest.xml 에 다음을 넣으면 된단다.
<application> 태그 안에 <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
3. 윈도우 모바일[각주:6]
IMEI 는 lineGetGeneralInfo 라는 TAPI 함수로 알아낼 수 있다.
DWORD GetTapiIMEI(HLINE hLine, CString &a_strIMEI)
{
  DWORD            rc;
  LINEGENERALINFO    LineGeneralInfo;
  TCHAR            lpszSerialNumber[32];

  memset( &LineGeneralInfo, 0, sizeof(LineGeneralInfo) );
  LineGeneralInfo.dwTotalSize  = sizeof(LineGeneralInfo);
  rc = lineGetGeneralInfo(hLine, &LineGeneralInfo);
  if (0 == rc)
  {
    if (LineGeneralInfo.dwTotalSize < LineGeneralInfo.dwNeededSize)
    {
      LINEGENERALINFO *lpBuffer = (LINEGENERALINFO *)malloc(LineGeneralInfo.dwNeededSize);
      lpBuffer->dwTotalSize  = LineGeneralInfo.dwNeededSize;
      rc = lineGetGeneralInfo(hLine, lpBuffer);
      memcpy( lpszSerialNumber,
               (TCHAR*)((lpBuffer)+lpBuffer->dwSerialNumberOffset),
               lpBuffer->dwSerialNumberSize );
      free(lpBuffer);
    }
    else
    {
      memcpy( lpszSerialNumber,
               (TCHAR*)((&LineGeneralInfo)+LineGeneralInfo.dwSerialNumberOffset),
               LineGeneralInfo.dwSerialNumberSize  );
    }
    a_strIMEI = lpszSerialNumber;
  }
  return rc;
}
이 함수는 Privileged Function 으로 이를 사용하는 프로그램은 Privilege Certificate 으로 서명을 해야한다.

4. 노키아(심비안 S60 3rd platform)[각주:7]
다음의 헤더 파일과 소스 파일을 만들자.
헤더파일
#ifndef __SYSTEM_MANAGER_H__
#define __SYSTEM_MANAGER_H__
#include <etel3rdparty.h>

class CystemManager : public CActive{
public:
        typedef enum {EHandsetIMEI, EHandsetIMSI, EHandsetNetworkInfo } InfoType;

public:
        static CystemManager* NewL();
        ~CSystemManager();

public: // New functions
        void StartL();  // Request
        const TPtrC GetIMEI();
        const TPtrC GetIMSI();
        void GetNetworkInfoL(TUint& aLocation, TUint& aCellId);
private:
        // C++ constructor
        CSystemManager();
        // Second-phase constructor
        void ConstructL();
        // From CActive
        void RunL();
        // Cancel
         void DoCancel();
private:
        enum TGetInfoState {EStart = 1, EGetPhoneInfo, EDone};

private:
        InfoType iPhoneInfoType;
        TInt iState; // State of the active object
        CTelephony* iTelephony;
        CTelephony::TPhoneIdV1 iPhoneId;
        CTelephony::TSubscriberIdV1 iSubscriberId;
        CTelephony::TNetworkInfoV1 iNetworkInfo;
        CActiveSchedulerWait iActiveSchedulerWait;
        TBuf<ctelephony::kphoneserialnumbersize> iIMEI;
        TBuf<ctelephony::kimsisize> iIMSI;
        TUint iCellId;
        TUint iLocationAreaCode;
};
#endif // __SYSTEM_MANAGER_H__
소스 파일
// System includes
#include <badesca.h>
#include <e32std.h>
#include <eikenv.h>
#include <eikappui.h>
#include <eikapp.h>
#include <etelbgsm.h>

//User includes
#include "SystemManager.h"
CSystemManager* CSystemManager::NewL()
{
  CSystemManager* self = new (ELeave) CSystemManager();
  CleanupStack::PushL(self);
  self->ConstructL();
  CleanupStack::Pop(self);
  return self;
}

CSystemManager::CSystemManager()
 : CActive(EPriorityHigh), // HIGH priority
   iPhoneInfoType(EHandsetIMEI),
   iState(EStart),
   iTelephony(NULL),
   iIMEI(0),
   iIMSI(0),
   iCellId(0),
   iLocationAreaCode(0)
{
}

void CSystemManager::ConstructL()
{
  iTelephony = CTelephony::NewL();
  CActiveScheduler::Add(this);
 // Add to scheduler
}

CSystemManager::~CSystemManager()
{
  Cancel(); // Cancel any request, if outstanding
  // Delete instance variables if any
  delete iTelephony;
}

void CSystemManager::DoCancel()
{
  switch(iPhoneInfoType)
  {
  case EHandsetIMEI:
       iTelephony->CancelAsync(CTelephony::EGetPhoneIdCancel);
      break;
  case EHandsetIMSI:
      iTelephony->CancelAsync(CTelephony::EGetSubscriberIdCancel);
      break;
  default:
      iTelephony->CancelAsync(CTelephony::EGetCurrentNetworkInfoCancel);
      break;
  }
}

void CSystemManager::StartL()
{
  Cancel(); // Cancel any request, just to be sure
  iState = EGetPhoneInfo;
  switch(iPhoneInfoType)
  {
  case EHandsetIMEI:
    {
      CTelephony::TPhoneIdV1Pckg phoneIdPckg( iPhoneId );
      iTelephony->GetPhoneId(iStatus, phoneIdPckg);
    }
    break;
  case EHandsetIMSI:
    {
      CTelephony::TSubscriberIdV1Pckg subscriberIdPckg( iSubscriberId );
      iTelephony->GetSubscriberId(iStatus, subscriberIdPckg);
    }
    break;
  case EHandsetNetworkInfo:
    {
      CTelephony::TNetworkInfoV1Pckg networkInfoPckg( iNetworkInfo );
      iTelephony->GetCurrentNetworkInfo(iStatus, networkInfoPckg);
    }
    break;
  }
  SetActive(); // Tell scheduler a request is active
  iActiveSchedulerWait.Start();
}

void CSystemManager::RunL()
{
  iState = EDone;
  if ( iActiveSchedulerWait.IsStarted() )
  {
    iActiveSchedulerWait.AsyncStop();
    if(iStatus == KErrNone)
    {
      switch(iPhoneInfoType)
      {
      case EHandsetIMEI:
        iIMEI.Append(iPhoneId.iSerialNumber ); break;
      case EHandsetIMSI:
        iIMSI.Append(iSubscriberId.iSubscriberId ); break;
      case EHandsetNetworkInfo:
        iCellId = iNetworkInfo.iCellId;
        iLocationAreaCode = iNetworkInfo.iLocationAreaCode;
        break;
      }
    }
    else
    {
		   // ***********Handle Error here ************
    }
  }
}

const TPtrC CSystemManager::GetIMEI()
{
  iPhoneInfoType = EHandsetIMEI;
  iIMEI.Zero();
  StartL();
  TPtrC ptr(iIMEI.Ptr());
  return ptr;
}

const TPtrC CSystemManager::GetIMSI()
{
  iPhoneInfoType = EHandsetIMSI;
  iIMSI.Zero();
  StartL();
  TPtrC ptr(iIMSI.Ptr());
  return ptr;
}

void CSystemManager::GetNetworkInfoL(TUint& aLocationCode, TUint& aCellId)
{
  iPhoneInfoType = EHandsetNetworkInfo;
  StartL();
  aCellId = iCellId;
  aLocationCode = iLocationAreaCode;
  return;
}
단, "ReadDeviceData" 권한이 있어야 함.

혹은 다음과 같은 방법으로 IMSI를 찾아볼 수도 있단다.[각주:8],[각주:9]
RMobilePhone::GetSubscriberId()
 
스마트폰이 없어 테스트를 못했다 -.-; 그럼에도 게시물을 올리는 이유는 이것이 도움 내지 다른 정보를 찾는 시발점이 될 수 있도록 하기 위함이다.

이제 아이폰에서 궁금증은 어떻게 IMSI 값을 찾아낼 수 있는 것인가 이다.

* 2010/07/28에 수정함(안드로이드쪽)




  1. http://stackoverflow.com/questions/823181/how-to-get-imei-on-iphone [본문으로]
  2. 겉보기에는 애플은 사용자 정보를 노출하지 않기를 원하는 것 같아보인다. 사실 구조상 완전히 막을 수는 없음과 똑같은 클래스내에 인터페이스만 막는 얍삽한 방법을 쓴걸 보면... 애플은 8비트 시절 향수가 그리운건가? 제품을 만드는 방향도 해커에게 흥미거리를 던져주는 방향인 것 같다. 아마도 애플이 이렇게 배포본과 개발용 버전을 구별하는 건 고급기술(?)은 가리고 이 부분을 지원하여 이익을 추구하거나 자사 제품이 보안성이 뛰어나다는 헛소리를 받쳐주는 비지니스 전략이 아닌가 생각한다. 어짜피 제품 사용자가 많아지면 빈틈이 많아지고 보안성이 떨어지는 건 당연한다. 그 만큼 안 쓰니까 보안성이 좋다고 하는 것 뿐이다. [본문으로]
  3. http://www.cocoachina.com/bbs/read.php?tid=7130&page=2#63378 에서 발췌 [본문으로]
  4. Android 소스 [본문으로]
  5. http://www.androidpub.com/26015 [본문으로]
  6. http://social.msdn.microsoft.com/forums/en-US/vssmartdevicesnative/thread/4ad0b002-330a-47f4-b98f-12a891b9d4b1 [본문으로]
  7. http://www.newlc.com/en/Retrieving-IMEI-IMSI-Network-Info.html [본문으로]
  8. http://www.newlc.com/en/How-to-retrive-the-IMSI-number.html [본문으로]
  9. http://www.newlc.com/en/topic-15159 [본문으로]
댓글