IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> Android加密存储之Security源码分析和基本用法介绍|Android敏感信息通过AES256加密存储 -> 正文阅读

[网络协议]Android加密存储之Security源码分析和基本用法介绍|Android敏感信息通过AES256加密存储

篇章目标要点

国家法律法规越来越注重用户信息安全的问题,针对敏感个人信息也要求实行必要的加密存储手段,比如针对手机号/身份证/密码等敏感信息需要进行加密存储。Androidx提供了Security框架可以支持必要信息的加密和解密。

Security源码分析

Security的加密逻辑是在google开发的Tink框架中实现的,其关键的类和方法信息如下文所述。
其中StreamingAhead是一个接口类,其实现类包括AesCtrHmacStreaming和AesCtrHkdfStreaming分别对应的是两种不同的加密方式。这次选择AesCtrHmacStreaming作为了解其加密原理的研究对象。StreamingAeadEncryptingStream类中
加密写入流源码如下

      NonceBasedStreamingAead streamAead, OutputStream ciphertextChannel, byte[] associatedData)
      throws GeneralSecurityException, IOException {
    super(ciphertextChannel);
    encrypter = streamAead.newStreamSegmentEncrypter(associatedData);
    plaintextSegmentSize = streamAead.getPlaintextSegmentSize();
    //明文Buffer
    ptBuffer = ByteBuffer.allocate(plaintextSegmentSize);
    //密文Buffer
    ctBuffer = ByteBuffer.allocate(streamAead.getCiphertextSegmentSize());
    //明文Buffer大小的修正:去除Header和首帧数据offset
    ptBuffer.limit(plaintextSegmentSize - streamAead.getCiphertextOffset());
    ByteBuffer header = encrypter.getHeader();
    byte[] headerBytes = new byte[header.remaining()];
    header.get(headerBytes);
    //先写密文的头部数据
    out.write(headerBytes);
    open = true;
  }

写入数据

  @Override
  public synchronized void write(byte[] pt, int offset, int length) throws IOException {
    if (!open) {
      throw new IOException("Trying to write to closed stream");
    }
    int startPosition = offset;
    int remaining = length;
    //循环进行数据加密
    while (remaining > ptBuffer.remaining()) {
      int sliceSize = ptBuffer.remaining();
      ByteBuffer slice = ByteBuffer.wrap(pt, startPosition, sliceSize);
      startPosition += sliceSize;
      remaining -= sliceSize;
      try {
        ptBuffer.flip();
        ctBuffer.clear();
        //对本次处理的数据转为密文
        encrypter.encryptSegment(ptBuffer, slice, false, ctBuffer);
      } catch (GeneralSecurityException ex) {
        throw new IOException(ex);
      }
      ctBuffer.flip();
      //io写入转换的密文片段
      out.write(ctBuffer.array(), ctBuffer.position(), ctBuffer.remaining());
      ptBuffer.clear();
      ptBuffer.limit(plaintextSegmentSize);
    }
    //更新待转换明文的起点指针,同时更新剩余的待转换数据长度
    ptBuffer.put(pt, startPosition, remaining);
  }

加密算法的执行

    @Override
    public synchronized void encryptSegment(
        ByteBuffer plaintext, boolean isLastSegment, ByteBuffer ciphertext)
        throws GeneralSecurityException {
      int position = ciphertext.position();
      byte[] nonce = nonceForSegment(noncePrefix, encryptedSegments, isLastSegment);
      cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(nonce));
      encryptedSegments++;
      //执行向密文的转换
      cipher.doFinal(plaintext, ciphertext);
      ByteBuffer ctCopy = ciphertext.duplicate();
      ctCopy.flip();
      ctCopy.position(position);
      mac.init(hmacKeySpec);
      mac.update(nonce);
      mac.update(ctCopy);
      byte[] tag = mac.doFinal();
      ciphertext.put(tag, 0, tagSizeInBytes);
    }

针对Buffer加密过程位于CypherSpi类代码如下,内部核心业务在engineUpdate和engineDoFinal方法中,由于暂未找到源码,暂时无法进一步阅读。

    private int bufferCrypt(ByteBuffer var1, ByteBuffer var2, boolean var3) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
        if (var1 != null && var2 != null) {
            int var4 = var1.position();
            int var5 = var1.limit();
            int var6 = var5 - var4;
            if (var3 && var6 == 0) {
                return 0;
            } else {
                int var7 = this.engineGetOutputSize(var6);
                if (var2.remaining() < var7) {
                    throw new ShortBufferException("Need at least " + var7 + " bytes of space in output buffer");
                } else {
                    boolean var8 = var1.hasArray();
                    boolean var9 = var2.hasArray();
                    byte[] var10;
                    int var11;
                    byte[] var12;
                    int var13;
                    int var14;
                    int var23;
                    if (var8 && var9) {
                        var10 = var1.array();
                        var11 = var1.arrayOffset() + var4;
                        var12 = var2.array();
                        var13 = var2.position();
                        var14 = var2.arrayOffset() + var13;
                        if (var3) {
                            var23 = this.engineUpdate(var10, var11, var6, var12, var14);
                        } else {
                            var23 = this.engineDoFinal(var10, var11, var6, var12, var14);
                        }

                        var1.position(var5);
                        var2.position(var13 + var23);
                        return var23;
                    } else {
                        int var16;
                        if (!var8 && var9) {
                            int var19 = var2.position();
                            byte[] var21 = var2.array();
                            int var20 = var2.arrayOffset() + var19;
                            byte[] var22 = new byte[getTempArraySize(var6)];
                            var14 = 0;

                            do {
                                var23 = Math.min(var6, var22.length);
                                if (var23 > 0) {
                                    var1.get(var22, 0, var23);
                                }

                                if (!var3 && var6 == var23) {
                                    var16 = this.engineDoFinal(var22, 0, var23, var21, var20);
                                } else {
                                    var16 = this.engineUpdate(var22, 0, var23, var21, var20);
                                }

                                var14 += var16;
                                var20 += var16;
                                var6 -= var23;
                            } while(var6 > 0);

                            var2.position(var19 + var14);
                            return var14;
                        } else {
                            if (var8) {
                                var10 = var1.array();
                                var11 = var1.arrayOffset() + var4;
                            } else {
                                var10 = new byte[getTempArraySize(var6)];
                                var11 = 0;
                            }

                            var12 = new byte[getTempArraySize(var7)];
                            var13 = var12.length;
                            var14 = 0;
                            boolean var15 = false;

                            do {
                                var16 = Math.min(var6, var13 == 0 ? var10.length : var13);
                                if (!var8 && !var15 && var16 > 0) {
                                    var1.get(var10, 0, var16);
                                    var11 = 0;
                                }

                                try {
                                    int var17;
                                    if (!var3 && var6 == var16) {
                                        var17 = this.engineDoFinal(var10, var11, var16, var12, 0);
                                    } else {
                                        var17 = this.engineUpdate(var10, var11, var16, var12, 0);
                                    }

                                    var15 = false;
                                    var11 += var16;
                                    var6 -= var16;
                                    if (var17 > 0) {
                                        var2.put(var12, 0, var17);
                                        var14 += var17;
                                    }
                                } catch (ShortBufferException var18) {
                                    if (var15) {
                                        throw (ProviderException)(new ProviderException("Could not determine buffer size")).initCause(var18);
                                    }

                                    var15 = true;
                                    var13 = this.engineGetOutputSize(var16);
                                    var12 = new byte[var13];
                                }
                            } while(var6 > 0);

                            if (var8) {
                                var1.position(var5);
                            }

                            return var14;
                        }
                    }
                }
            }
        } else {
            throw new NullPointerException("Input and output buffers must not be null");
        }
    }

对明文的加密过程在CipherSpi的子类中予以实现,由于自用工具限制暂时还未找到相关类,在此放置一张加密过程的原理框图
在这里插入图片描述

加密存储和解密用法

本节简要阐述如何使用Security进行加密和解密,将以一个存储手机号为例展示其基本用法
1.完成效果
在这里插入图片描述
(2)添加依赖

    //安全相关依赖
    implementation "androidx.security:security-crypto:1.0.0"

(3)封装加密工具
通过对EncryptedFile的编码写入和解码读取进行封装,简化外部的调用。其中每一个存储的敏感信息都需要指定一个String类型的key,作为加密文件保存所用的名称。选择的加密方式为AES256位长度密钥的对称性加密算法。

/**
 * 执行加密存储和解密读取的工具类, 需要外部为存储的加密字符串设置key值,key值加上".txt"后缀将作为加密文件名<P/>
 * 加密后的文件放在同一文件夹中
 */
public class SecurityFile {
    private KeyGenParameterSpec mKeyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
    //加密文件存放位置
    private final String DIR_ENCRYPTED_FILE = "/data/data/"+ SecurityApp.getInstance().getPackageName() + "/encrypted";

    /**
     * 加密存储字符串
     * @param inputString 待存储的字符串
     */
    public void encode(String inputString, String key){
        try{
            String mainKeyAlias = MasterKeys.getOrCreate(mKeyGenParameterSpec);
            File dir = new File(DIR_ENCRYPTED_FILE);
            if(!dir.exists()){
                dir.mkdir();
            }
            //创建加密文件
            EncryptedFile encryptedFile = new EncryptedFile.Builder(new File(DIR_ENCRYPTED_FILE +"/" +key+".txt"),
                    SecurityApp.getInstance(), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build();
            //将待存储字符串写入文件
            OutputStream outputStream = encryptedFile.openFileOutput();
            outputStream.write(inputString.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();
        }catch (GeneralSecurityException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    /**
     * 解密存储的字符串
     * @param key 字符串key值
     * @return 返回解密后的字符串
     */
    public String decode(String key){
        try{
            String mainKeyAlias = MasterKeys.getOrCreate(mKeyGenParameterSpec);
            File dir = new File(DIR_ENCRYPTED_FILE);
            if(!dir.exists()){
                return null;
            }
            //创建加密文件
            EncryptedFile encryptedFile = new EncryptedFile.Builder(new File(DIR_ENCRYPTED_FILE, key+".txt"),
                    SecurityApp.getInstance(), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build();
            //读取加密文件内容
            InputStream inputStream = encryptedFile.openFileInput();
            ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
            int nextByte = inputStream.read();
            while (nextByte != -1){
                arrayOutputStream.write(nextByte);
                nextByte = inputStream.read();
            }
            byte[] bytes = arrayOutputStream.toByteArray();
            inputStream.close();
            arrayOutputStream.close();
            //转为字符串
            return new String(bytes);
        }catch (GeneralSecurityException e){
            e.printStackTrace();
            return null;
        }catch (IOException e){
            e.printStackTrace();
            return null;
        }
    }
}

(4)在AndroidManifest添加读写权限

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

(5)调用示例
写了一个很简单的布局展示存储和读取效果,在此就不展示layout定义了,调用的代码示例如下

public class MainActivity extends AppCompatActivity {
    //执行加密存储和读取的对象
    private SecurityFile mSecurityFile;
    private Button mButtonOriginText, mButtonReadText;
    private TextView mTextViewOriginText, mTextViewReadText;
    //待加密存储的手机号
    private final static String MOBILE_PHONE_NO = "13632211233";
    //手机号存储时所用的key
    private final static String MOBILE_KEY = "mobile";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSecurityFile = new SecurityFile();
        initView();
    }

    private void initView(){
        mButtonOriginText = findViewById(R.id.button_origin_text);
        mButtonReadText = findViewById(R.id.button_read_text);
        mTextViewOriginText = findViewById(R.id.textview_origin_text);
        mTextViewReadText = findViewById(R.id.textview_read_text);
        //点击时加密写入明文
        mButtonOriginText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTextViewOriginText.setText(MOBILE_PHONE_NO);
                mSecurityFile.encode(MOBILE_PHONE_NO, MOBILE_KEY);
                Toast.makeText(MainActivity.this, "手机号加密存储成功",Toast.LENGTH_SHORT).show();
            }
        });

        //点击时解密读取密文
        mButtonReadText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String decodeString = mSecurityFile.decode(MOBILE_KEY);
                mTextViewReadText.setText(decodeString);
                Toast.makeText(MainActivity.this, "手机号信息解密读取成功",Toast.LENGTH_SHORT).show();
            }
        });
    }
}

学习心得

以上即是全部使用过程,了解后可以掌握敏感信息的加密存储。缺失的内容是对明文的加密过程是在CipherSpi的子类中予以实现,由于自用工具限制暂时还未找到相关类,后续会补充相关的阅读心得

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-10-12 23:49:56  更:2021-10-12 23:53:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 3:40:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码