Android安全篇

        从广义上讲,智能终端的安全主要涉及硬件、通信、软件、信息等4个层面,其中硬件的安全包括设备的跟踪/定位、防摔、防尘、防水、防震、Flash的防磨损、电磁兼容、触摸屏防划等;通信的安全主要指通信链路的安全,涉及蜂窝、WiFi、BT、互联网等的通信,相关的技术包括防窃听、防拦截、防病毒、防入侵等;软件的安全包括防破解、防反编译、防抄袭等;信息的安全包括隐私保护、敏感数据的保护、数据加密等。随着移动互联网的不断发展,智能终端的安全受到人们的重视,同时诞生了很多创新的机会。

        为了更好地保护系统私有数据的安全、完整及版权信息、Android在系统的不同层面提供了多种安全机制,其中Android的主要安全机制包括Java混淆器、接入权限、数字证书、SSL、数据库安全、虚拟机(安全沙箱)、文件访问控制等。Android的安全框架如下图:

       

        代码的安全主要是通过Java混淆器实现。虚拟机过于底层,不进行详述。

        基于SSL可以实现安全的网络通信,但数据的安全在Android中需要依据不同的应用场景采用不同的方案,目前Android中集成的SQLite虽然提供了加密算法的接口,但却并为集成加密算法,数据库的安全需要OEM商自行实现。对于应用层开发,需要考虑数据库的接入权限。

        在文件访问控制方面,系统运行时,文件的访问控制由Linux系统提供,其中system.img所在的分区是只读的,不允许用户写入,而data.img所在的分区是可读写的,用于存放用户数据。分区的用户权限在init.rc中定义。

        除了以上机制外,在数据安全方面,对于固定的敏感数据等建议通过原生代码实现,以避免被反编译,对于动态的敏感数据,建议进行加密存储。

1.Java混淆器

        出于跨平台的需求,Java的运行和C/C++不同,Java是通过Java虚拟器托管字节码来运行的,正是由于这一特性,Java存在着可反汇编的特性,并且随着反编译技术的进步,除了变量名等运行时信息外,设计的框架和语言的组织在反编译技术下已经无秘密可言,这严重威胁了企业的商业利益,也带来了潜在的风险,因此有必要增强反编译手段,这方面的努力通常是通过混淆器进行的。混淆器的目的在于打开字节码的布局,从而增加反编译的难度。目前,Android自带的Java混淆器为著名的proguard。

        在源代码编译中,如果希望利用Java混淆器,方法为在Android.mk中增加如下配置:

                LOCAL_PROGUARD_FLAG_FILES :=proguard.flags

        proguard.flags声明了不进行混淆的类或类的特定方法。下面是packages\apps\Launcher2\proguard.flags的实现:

                -keep class com.android.launcher2.Launcher{        //特定方法

                        public void previousScreen(android.view.View);

                        public void nextScreen(android.view.View);

                        public void launchHotseat(android.view.View);

                }

                -keep class com.android.launcher2.AllAppsS$Defines{        //特定类,“$”表示后面的类是前面的类的内部类

                       *;

                }

                -keep class com.android.launcher2.ClippedImageView{

                        *;

                }

        在Android本身的工具中,通过dexdump也可以查看DEX字节码的执行情况。

2.接入权限

        在Android中,接入权限分为4个等级,即normal、dangerous、signature、signatureOrSystem等。权限的等级决定了进行安全保护的级别。其中normal权限不会给用户带来实质性的伤害,如调整背光等动作适合该权限;dangerous权限可能会给用户带来潜在的伤害,如读取电话簿、联网等动作,系统在安装应用时发现应用需要改权限会提示用户;signature权限要求有同一签名的应用间才能相互访问;signatureOrSystem权限主要被设备商使用。

        框架层的接入权限定义在frameworks\base\core\res\AndroidManifest.xml中。

        (1)创建接入权限

                创建接入权限非常简单,不过需要注意,和常规的理解不同,高级别权限并不兼容低级别权限,比如,对于normal接入权限,如果在两个拥有同样数字证书签名的应用间使用,则会导致失败。创建一个接入权限的方法如下:

                        <permission android:name="andoid.permission.GET_ACCOUNTS"

                                android:permissionGroup="android.permission-group.ACCOUNTS"

                                android:protectionLevel="normal"

                                android:description="@string/permdesc_getAccounts"

                                android:label="@string/permlab_getAccounts"/>

                创建一个接入权限组的方法如下:

                        <permission-group android:name="android.permission-group.STORAGE"

                                android:label="@string/permgrouplab_storags"

                                android:description="@string/permgroupdec_storage" />

                对于signatureOrSystem级别的权限,在基于SDK开发的环境中,在调试应用时无法接入这个级别权限的服务。这点在进行源代码级别开发时需要注意。

        (2)应用权限

                应用权限主要用来对应用的操纵增加限制,防止恶意应用的非法操作造成敏感数据泄露、设备被非法控制,以及恶意收费等,设置应用权限的两种方式:一种是设置共享用户ID;另一种是直接设置应用或组件的接入权限。

                1)设置共享用户ID

                        Android在权限管理上应用了Linux的ACL权限机制,而非早期UNIX采用的UGO权限机制。通过在每个应用中使用sharedUserId属性可共享系统账户权限。

                        目前,Android支持的sharedUserId属性包括com.android.cts.shareduid、com.android.cts.process.uidpid_test、android.uid.system、com.android.uid.test、android.uid.calendar、android.media、com.android.framework.extrnalsharedpermstestapp、android.uid.shared、android.uid.phone等,其中常用的是android.uid.system、android.media、android.uid.shared等。

                        Android源代码树携带的系统证书包括media、platform、shared、testkey等,其中media证书用于多媒体和下载场景中;platform证书用于系统场景中;shared证书用于启动器和电话簿场景总;testkey证书用于开发场景中。这些证书位于build\target\product\security\目录下。产生系统证书的方法如下:

                             #development/tools/make_key testkey '/C=US/ST=Califormia/L=MountainView/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'

                             #development/tools/make_key platform '/C=US/ST=Califormia/L=MountainView/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'

                             #development/tools/make_key shared '/C=US/ST=Califormia/L=MountainView/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'

                             #development/tools/make_key media '/C=US/ST=Califormia/L=MountainView/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'

                        将PK8格式的密钥转换为PEM格式的方法如下:

                                #openssl pkcs8 -inform DER -nocrypt -in testkey.pk8 -out testkey.pem

                        通过PEM格式的密钥生成签名的方法如下:

                               #openssl dgst -binary -shal -sign testkey.pem FILE > FILE.sig

                        通过设置用户ID可以将应用运行在共享的进程中,这一设置通常和在Android.mk中设置本地证书同时使用。

                        如果希望获得系统权限,那么应设置sharedUserId属性为android.uid.system。对APK使用系统证书签名的方法如下:

                               #java -jar SignApk.jar platform.x509.pem platform.pk8 unsigned.apk signed.apl

                        如果APK已经签名,那么在APK解压后的META-INF目录下可以看到相应的证书。目前采用的签名算法为RAS算法。

                        需要注意,当签名证书无法满足用户权限时,在运行应用时,会抛出SecurityException异常,如android.permission.BIND_APPWIDGET权限要求用户具备系统级权限,当签名证书不正确时,就会抛出如下异常:

                                ERROR/AndroidRuntime(3759); java.lang.SecurityException; Neither user 10024 nor current process has android.permission.BIND_APPWIDGET,其中10024即为系统UID。

                2)直接设置应用的接入权限

                        设置应用的接入权限的方法非常简单,例如为了调试BT,需进行如下设置:

                                <uses-permission android:name="android.permission.BLUETOOTH" />

                                <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

                3)设置Activity的接入权限

                        权限仅作用于单个的Activity,方法如下:

                                <activity android:name="com.android.server.ShutdownActivity"

                                        android:permission="android.permission.SHUTDOWN"        //关机权限

                                        android:excludeFromRecents="true">

                                        <intent-filter>

                                               <action android:name="android.intent.action.ACTION_REQUEST_SHUTDOWN"/>

                                               <category android:name="android.intent.category.DEFAULT"/>

                                       </intent-filter>

                                 </activity>

                 4)数据库接入权限

                        对于数据库的接入权限主要分为3个层次,即读、写、搜索,相关的配置实例如下:

                                <provider android:name="SuggestionsProvide"

                                        android:readPermission="android.permission.READ_SMS"

                                        android:authorities="com.android.mms.SuggestionProvider“>

                                        <path-permission        //搜索权限

                                                android:pathPrefix="/seatch_suggest_query"

                                                android:readPermission="android.permission.GLOBAL_SEARCH"/>

                                        <path-permission

                                                android:pathPrefix="/seatch_suggest_shortcut"

                                                android:readPermission="android.permission.GLOBAL_SEARCH"/>

                                 </provider>

        (3)权限验证

                通过权限对组件进行保护带来了一个问题,即如何判断调用方是否具有相应的权限。Android为此提供了多种方法,分别适用于不同的场景。

                尽在受保护的组件的上下文中进行权限验证,方法如下:

                        public abstract int cheakCallingOrSelfPermission(String permission)

                        public abstract int checkCallingPermission(String permission)

                        public abstract int checkPermission(String permission, int pid, int uid)

                要在第三方上下文中验证,其方法如下:

                        public abstract void enforcePermission(String permission, int pid, int uid, String message)

                当前,Android仅支持对调用者的进程信息进行提取,方法如下:

                        Binder.getCallingPid()

                        Binder.getCallingUid()

                对于特定的Uri,有读权限和写权限,进行权限验证的方法如下:

                        public abstract int checkCallingUriPermission(Uri uri, int modeFlags)

                        public abstract int checkUriPermission(Uri uri, int pid, int uid, int modeFlags)

                        public abstract int checkUriPermission(Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags)

                如果调用方拥有相应的权限,则权限验证的返回值为PackageManager.PERMISSION_GRANTED,否则返回PackageManager.PERMISSION_DENIED。

        (4)接入输入

                提供敏感信息支持的服务必须保证对非接入的阻止,在Android中,这是通过权限和证书等机制来实现的。以墙纸为例,要对服务进行权限保护,方法如下:

                        <service android:name="com.android.internal.service.wallpaper.ImageWallpaper"

                               android:permission="android.permission.BIND_WALLPAPER">

                        </service>

                要在服务的方法被调用时验证接入的合法性,方法如下:

                        private int enforceAccessPermission(){

                                int ret=mContext.checkCallingOrSelfPermission("android.permisson.BIND_WALLPAPER");

                                return ret;

                        }

                然后在调用的方法开始处进行接入合法性判断,方法如下:

                        public boolean getSth(){

                                if(enforceAccessPermission() !=PackageManager.PERMISSION_GRANTED){

                                        return false;

                                }

                        }

        (5)框架层接入限制

                在框架层,为了隐藏某些方法或实现类,避免被第三方开发者开发的应用程序(基于SDK)调用,提供了hide限制的方法。

                hide限制根据作用的对象不同,可分为hide方法限制和hide类限制。

                hide方法限制的实现方法如下:

                        protected boolean isVerticalScrollBarHidden(){

                                return mFastScroller != null && mFastScroller.isVisible();

                        }

                hide类限制的实现方法如下:

                        public class WifiService extends IWifiManager.Stub{

                        }

                当框架层的API发生变化时,需要先通过make update-api来更新current.xml,否则会因为一致性问题导致系统编译上的失败。

                如果确实需要在基于SDK开发时在应用层引用隐藏的方法或类,那么可以采用Java反射机制达到这一目的,但是考虑到系统的兼容性,除非确实必要,不然不建议开发者采取此类方式来实现。

                在基于源代码开发时,hide限制无效。

3.数字证书

        在Android中,每个应用在发布时均必须拥有自己的数字证书签名,这个数字证书签名用于在应用的作者和应用程序之间建立信任关系。

        事实上,数字证书签名一直存在,即使是通过SDK进行开发的阶段,每次运行应程序时SDK均会自动生成一个用于调试模式的数字证书签名。只有在应用正式发布时,才会用到发布模式的数字证书签名。

        数字证书签名机制有利于程序升级,除了判断包名外,只有当新版应用和旧版应用的数字签名相同时,Android才会认为这两个程序是同一个应用的不同版本。另外Android允许拥有相同数字签名的应用运行在同一个进程中,这同样有利于在多个应用中共享数据。

        在进行数字证书签名时,需要考虑证书的有效期问题,对于从应用商店上下载的应用,如果数字证书过期,意味着持有该数字签名证书的应用将不能正常升级。而Android Markets强制要求所有应用程序的数字签名的有效期要持续到2033年10月22日以后。

        在发布应用时,开发者通过两种方式来为自己的APK签名。

                在命令行方式下利用Keytool来生成数字证书,并利用Jarsigner来为APK进行数字签名。

                使用ADTExport Wizard进行签名。

        由于通过ADT Export Wizard进行数字证书生成和签名的方法非常简单,这里就不多做介绍了。使用Keytool生成数字证书的方式如下:

                #keytool -genkey -v -keystore android.keystore -alias miaozl -keyalg RAS -validity 20000

        上述代码中,keystore android.keystore表示生成的数字证书为android.keystore, 可以加上路径(默认在用户主目录下);alias miaozl表示数字证书的别名是miaozl;keyalg RAS表示采用的是RAS算法;validity 20000表示数字证书的有效期是20000天。另外通过keypass可以设置数字证书私钥的密码;通过keysize可以设置算法的位长,默认为1024bit,推荐2048bit及更长;通过storepass可以设置数字证书的密码。

        数字证书生成后,即可用来进行应用程序的签名了,方法如下:

                #jarsigner -verbose -keystore android.keystore demo.apk   证书别名

        接下来jarsigner会提示输入密钥库的口令和证书别名的口令,全部输入后,即可完成签名。

        具有相同数字证书的应用程序可以彼此分享数据和执行调用。查看应用程序是否已经签名的方法如下:

                #jarsigner -verify demo.apk

        查看数字签名证书的更详细信息的方法如下:

                #jarsigner -verify -verbose demo.apk

                #jarsigner -verify -verbose -certs demo.apk        //certs选项可以显示”CN=“,揭示谁创建了密钥

        在进行数字证书签名后,可用zipalign工具来优化应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值