Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AES加解密(三)——自定义文件头解密安卓实现 #237

Open
3 tasks
soapgu opened this issue Mar 4, 2024 · 0 comments
Open
3 tasks

AES加解密(三)——自定义文件头解密安卓实现 #237

soapgu opened this issue Mar 4, 2024 · 0 comments

Comments

@soapgu
Copy link
Owner

soapgu commented Mar 4, 2024

  • 前言

这次代码实现目标

  • 探索一下在安卓端端AES的加密实现
  • 针对HTTP的Body的流式处理的实现
  • 统计下对不同文件大小的性能检测

相关部分加密AES规则

  • 首先写入1K大小扰乱随机头数据

  • 读取源文件的头部1K数据(小于1K则读取所有数据)

  • 使用AES加密算法完成加密后写入加密文件

  • 如果源文件大于1K)剩余文件内容追加进加密文件

  • 整体思路

基本上实现分为以下步骤

  1. 请求GET的相关文件HTTP请求,获取ResponseBody
  2. 对ResponseBody开始流式处理
  3. 去除1K随机扰乱数据
  4. 读取1KAES的密文
  5. AES解密并写入本地文件
  6. 拷贝剩余数据到本地文件
    整个流程的串联还是使用ReactiveX Java框架来实现
  • 获取ResponseBody

private Single<ResponseBody> downloadFile( Call call ){
        return Single.create(subscriber -> {
            Logger.i( "------Begin To Request Http Thread:%s",  Thread.currentThread().getId() );
            call.enqueue(new Callback() {
                @Override
                public void onFailure(@NonNull Call call, @NonNull IOException e) {
                    Logger.e(e,"------- Http Error:%s",  e.getMessage() );
                    subscriber.onError( e );
                }

                @Override
                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                    subscriber.onSuccess( response.body() );
                }
            });
        });
    }
  • 读取指定长度数据

在开发过程中,我需要读取1000多byte的数据。
当ResponseBody转化InputStream流式的时候,其实他的底层实现还是网络字节流,这样read的操作并不一定能读取到和缓冲区一样大小的数据,这样就需要一个“拼包”的处理,如果一次读不到够数的数据,需要把剩下的包凑进去

private byte[] readHeader( InputStream inputStream, int headLength ) throws IOException {
        byte[] header = new byte[headLength];

        int total = 0;
        int remain = headLength;
        do {
            byte[] headerBuffer = new byte[remain];
            int read = inputStream.read(headerBuffer);
            System.arraycopy(headerBuffer,0,header,total,read);
            total += read;
            remain = headLength - total;
            Logger.i("current read head %d / %d , remain %d", total, headLength,remain );
        } while (remain > 0);
        return header;
    }
  • AES解密实现

Andoid的原生SDK的javax.crypto实现了大多数的加解密的实现,使用起来也很简单

     /**
     * AES 解密
     * 使用CBC模式&PKCS7Padding
     * @param bytes 密文
     * @return 明文
     */
    private byte[] decryptHeader( byte[] bytes ){
        String ALGORITHM = "AES/CBC/PKCS7Padding";
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            SecretKey secretKey = new SecretKeySpec( HexUtils.hexStringToByteArray( "30980f98296b77f00a55f3c92b35322d898ae2ffcdb906de40336d2cf3d556a0" ) , "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(HexUtils.hexStringToByteArray("e5889166bb98ba01e1a6bc9b32dbf3e6"));
            Logger.i("---Begin decrypt----");
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
            return cipher.doFinal(bytes);
        } catch (InvalidAlgorithmParameterException | BadPaddingException | InvalidKeyException |
                 NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException e) {
            throw new RuntimeException(e);
        }

    }
  • 文件流式处理

对整个下载流式处理,包括进度的显示打印等

private Single<File> decryptFile( ResponseBody body, String filePath ){
        return Single.create( emitter -> {

            File file = new File(filePath);
            int headLength = 1024;
            if( body.contentLength() > 2048 ){
                headLength += 16;
            }

            byte[] buffer = new byte[4096];
            InputStream inputStream = body.byteStream();
            OutputStream outputStream = null;
            try {
                this.runOnUiThread( ()-> progressBar.setProgress( 0 ));
                long totalProcess = 0L;
                outputStream = new FileOutputStream(file);
                long skip = inputStream.skip(1024);
                totalProcess += skip;
                Logger.i( "Step 1: skip %d",skip );
                byte[] header = readHeader( inputStream , headLength );
                Logger.i( "Step 2: read header length %d",headLength );
                totalProcess += headLength;
                byte[] decryptedBytes = decryptHeader( header );
                Logger.i( "Step 3: decrypt header length %d",decryptedBytes.length );
                //Logger.i("decrypt content:%s",HexUtils.bytesToHex(decryptedBytes));
                outputStream.write(decryptedBytes, 0, decryptedBytes.length);
                int read;
                long contentLength = body.contentLength();
                long current = 0;
                DecimalFormat df = new DecimalFormat("#.##");
                do{
                    current ++;
                    read = inputStream.read(buffer);
                    //Logger.i( "read file length:%d",read );
                    if( read > 0 ){
                        totalProcess += read;
                        outputStream.write( buffer,0,read );
                        if( current % 10 == 0 ){
                            double percent = totalProcess * 100.00  / contentLength;
                            this.runOnUiThread( ()->{
                                msg.setText( String.format("%s %%",df.format( percent )) );
                                if( (int)percent > progressBar.getProgress() ) {
                                    progressBar.setProgress((int)percent);
                                }
                            });
                        }
                    }
                }while (read > 0);
                Logger.i( "process file content %s / %s ",totalProcess,contentLength );
                outputStream.flush();
                emitter.onSuccess( new File(filePath) );
            } catch (Exception e) {
                emitter.onError(e);
            }
            finally {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
                body.close();
            }
        } );

    }
  • 对文件操作的整体串联处理

private void productFile( String fileName ){
        tv_complete.setText("");
        Date start = new Date();
        String url = String.format("http://testing.heyshare.cn/download/%s",fileName);
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        Call call = client.newCall(request);
        String filePath = getExternalFilesDir(null) + "/" + fileName;
        disposable.add( downloadFile(call)
                .flatMap( body -> decryptFile(body,filePath) )
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe( file ->{
                            msg.setText( String.format( "文件%s解密完成",fileName ) );
                            Date end = new Date();
                            Long diff = end.getTime() - start.getTime() ;
                            tv_complete.setText( String.format( "用时 %s 毫秒", diff ));
                        },
                        throwable -> Logger.e(throwable,"download error")));
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant